Instance metadata/metadata providers overhaul (#1481)
* Instance metadata plus metadata provider overhaul. Fix consumption of wado-uri urls fallbacks + datatype agnosticism. WIP DICOMify things. fix various issues with naturalized variable naming migration. Remove metadata provider. Fix consumption of multiframe images and addition of CWIL metadata. Fix strange build issues. Fix CWIL style windowWidth to array from naturalized DICOM. Fix PT, CT, CR and DX issues for cornerstone + DX issues for vtkjs. Move color palette fetching down to the natuaralized JSON level. Remove unused StudyMetadataSummary Remove redundant dicom metadata dictionary. Working local + json routes. Fix SR read. Finished first round of testing + cleaned up debugging etc. * data => metadata for instance naturalizedJSON * Update dcmjs version * Correct github isssues. * Fix erroneously replaced files. * Danny's recommended changes. * Instance metadata plus metadata provider overhaul. Fix consumption of wado-uri urls fallbacks + datatype agnosticism. WIP DICOMify things. fix various issues with naturalized variable naming migration. Remove metadata provider. Fix consumption of multiframe images and addition of CWIL metadata. Fix strange build issues. Fix CWIL style windowWidth to array from naturalized DICOM. Fix PT, CT, CR and DX issues for cornerstone + DX issues for vtkjs. Move color palette fetching down to the natuaralized JSON level. Remove unused StudyMetadataSummary Remove redundant dicom metadata dictionary. Working local + json routes. Fix SR read. Finished first round of testing + cleaned up debugging etc. * data => metadata for instance naturalizedJSON * Update dcmjs version * Correct github isssues. * Fix erroneously replaced files. * Danny's recommended changes. * Update JSON CI * Update casing of import. * Fix jump for SR. * Fix unit tests for measurements service * Fix json CI test. * fix: update yarn lock * Fix local non-encapsulated pdf view * CI updated to new sucess message. Co-authored-by: Danny <danny.ri.brown@gmail.com>
This commit is contained in:
parent
8833731ad3
commit
1d85f7a74e
@ -5,7 +5,7 @@ An extension can register a SOP Class Handler Module by defining a
|
||||
the other modules, as it doesn't provide a `1:1` schema for UI or provide it's
|
||||
own components. It instead defines:
|
||||
|
||||
- `sopClassUids`: an array of string SOP Class UIDs that the
|
||||
- `sopClassUIDs`: an array of string SOP Class UIDs that the
|
||||
`getDisplaySetFromSeries` method should be applied to.
|
||||
- `getDisplaySetFromSeries`: a method that maps series and study metadata to a
|
||||
display set
|
||||
@ -15,13 +15,13 @@ A `displaySet` has the following shape:
|
||||
```js
|
||||
return {
|
||||
plugin: 'html',
|
||||
modality: 'SR',
|
||||
displaySetInstanceUid: 0,
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
```
|
||||
@ -50,7 +50,7 @@ export default {
|
||||
getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
return {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUids: Object.values(SOP_CLASS_UIDS),
|
||||
sopClassUIDs: Object.values(SOP_CLASS_UIDS),
|
||||
|
||||
/**
|
||||
* @param {object} series -
|
||||
@ -63,12 +63,12 @@ getSopClassHandlerModule({ servicesManager, commandsManager }) {
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUid: 0,
|
||||
displaySetInstanceUID: 0,
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# Lesion Tracker - Audit Trail
|
||||
|
||||
1. To view Audit Trails, select **View Audit Log** on the Configuration menu.
|
||||
2. To filter audit logs for a column or multiple columns, type or select in the related column or columns.
|
||||
2. To filter audit logs for a column or multiple columns, type or select in the
|
||||
related column or columns.
|
||||
|
||||

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

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

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

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

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

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

|
||||
|
||||
### Anonymize (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Send (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Export (Under Development)
|
||||
|
||||
...
|
||||
|
||||
### Delete (Under Development)
|
||||
|
||||
...
|
||||
|
||||
@ -63,7 +63,7 @@ const commandsModule = {
|
||||
*/
|
||||
const sopClassHandlerModule = {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
sopClassUids: Object.values({
|
||||
sopClassUIDs: Object.values({
|
||||
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
|
||||
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
|
||||
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
|
||||
@ -77,12 +77,12 @@ const sopClassHandlerModule = {
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
displaySetInstanceUid: 0, //utils.guid(),
|
||||
displaySetInstanceUID: 0, //utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@ -34,9 +34,9 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.12.0",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@ -51,7 +51,6 @@
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"query-string": "^6.8.3",
|
||||
"react-cornerstone-viewport": "^2.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,15 +8,6 @@ import handleSegmentationStorage from './handleSegmentationStorage.js';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
// Metadata configuration
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
};
|
||||
@ -58,34 +49,34 @@ class OHIFCornerstoneViewport extends Component {
|
||||
* Obtain the CornerstoneTools Stack for the specified display set.
|
||||
*
|
||||
* @param {Object[]} studies
|
||||
* @param {String} studyInstanceUid
|
||||
* @param {String} displaySetInstanceUid
|
||||
* @param {String} [sopInstanceUid]
|
||||
* @param {String} StudyInstanceUID
|
||||
* @param {String} displaySetInstanceUID
|
||||
* @param {String} [SOPInstanceUID]
|
||||
* @param {Number} [frameIndex=1]
|
||||
* @return {Object} CornerstoneTools Stack
|
||||
*/
|
||||
static getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex = 0
|
||||
) {
|
||||
if (!studies || !studies.length) {
|
||||
throw new Error('Studies not provided.');
|
||||
}
|
||||
|
||||
if (!studyInstanceUid) {
|
||||
if (!StudyInstanceUID) {
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
if (!displaySetInstanceUid) {
|
||||
if (!displaySetInstanceUID) {
|
||||
throw new Error('StudyInstanceUID not provided.');
|
||||
}
|
||||
|
||||
// Create shortcut to displaySet
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
@ -93,7 +84,7 @@ class OHIFCornerstoneViewport extends Component {
|
||||
}
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
if (!displaySet) {
|
||||
@ -107,17 +98,14 @@ class OHIFCornerstoneViewport extends Component {
|
||||
const stack = Object.assign({}, storedStack);
|
||||
stack.currentImageIdIndex = frameIndex;
|
||||
|
||||
if (sopInstanceUid) {
|
||||
if (SOPInstanceUID) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
'sopCommonModule',
|
||||
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
||||
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||
});
|
||||
|
||||
if (index > -1) {
|
||||
@ -134,39 +122,39 @@ class OHIFCornerstoneViewport extends Component {
|
||||
|
||||
getViewportData = async (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
let viewportData;
|
||||
|
||||
switch (sopClassUid) {
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
const specialCaseHandler =
|
||||
specialCaseHandlers[SOP_CLASSES.SEGMENTATION_STORAGE];
|
||||
|
||||
viewportData = await specialCaseHandler(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
viewportData = {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
|
||||
@ -179,31 +167,31 @@ class OHIFCornerstoneViewport extends Component {
|
||||
setStateFromProps() {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
sopClassUIDs,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (!studyInstanceUid || !displaySetInstanceUid) {
|
||||
if (!StudyInstanceUID || !displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sopClassUids && sopClassUids.length > 1) {
|
||||
if (sopClassUIDs && sopClassUIDs.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
'More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids && sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs && sopClassUIDs[0];
|
||||
|
||||
this.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
).then(viewportData => {
|
||||
this.setState({
|
||||
@ -221,9 +209,9 @@ class OHIFCornerstoneViewport extends Component {
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUid !==
|
||||
prevDisplaySet.displaySetInstanceUid ||
|
||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
this.setStateFromProps();
|
||||
|
||||
@ -247,6 +247,7 @@ const commandsModule = ({ servicesManager }) => {
|
||||
|
||||
if (enabledElement) {
|
||||
let viewport = cornerstone.getViewport(enabledElement);
|
||||
|
||||
viewport.voi = {
|
||||
windowWidth: Number(window),
|
||||
windowCenter: Number(level),
|
||||
|
||||
@ -6,25 +6,25 @@ import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { StackManager, DicomLoaderService } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.seriesInstanceUid === seriesInstanceUid;
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ function parseSeg(arrayBuffer, imageIds) {
|
||||
function addSegMetadataToCornerstoneToolState(
|
||||
segMetadata,
|
||||
toolState,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||
toolState
|
||||
@ -48,22 +48,22 @@ function addSegMetadataToCornerstoneToolState(
|
||||
const brushModule = cornerstoneTools.store.modules.brush;
|
||||
|
||||
for (let i = 0; i < segMetadata.length; i++) {
|
||||
brushModule.setters.metadata(displaySetInstanceUid, i, segMetadata[i]);
|
||||
brushModule.setters.metadata(displaySetInstanceUID, i, segMetadata[i]);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
@ -77,16 +77,16 @@ async function handleSegmentationStorage(
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same seriesInstanceUid. This is not supported yet...'
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
@ -119,8 +119,8 @@ async function handleSegmentationStorage(
|
||||
stack.currentImageIdIndex = 0;
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,29 +3,9 @@ import { SimpleDialog } from '@ohif/ui';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import csTools from 'cornerstone-tools';
|
||||
import merge from 'lodash.merge';
|
||||
import queryString from 'query-string';
|
||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||
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
|
||||
@ -58,16 +38,13 @@ export default function init({ servicesManager, configuration }) {
|
||||
};
|
||||
|
||||
const { csToolsConfig } = configuration;
|
||||
const { StackManager } = OHIF.utils;
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
const metadataProvider = OHIF.cornerstone.metadataProvider;
|
||||
|
||||
// ~~ Set our MetadataProvider
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
metadataProvider.get.bind(metadataProvider),
|
||||
9999
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
|
||||
// ~~
|
||||
const defaultCsToolsConfig = csToolsConfig || {
|
||||
globalToolSyncEnabled: true,
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
|
||||
const SUPPORTED_TOOLS = ['Length', 'EllipticalRoi', 'RectangleRoi', 'ArrowAnnotate'];
|
||||
const SUPPORTED_TOOLS = [
|
||||
'Length',
|
||||
'EllipticalRoi',
|
||||
'RectangleRoi',
|
||||
'ArrowAnnotate',
|
||||
];
|
||||
|
||||
const measurementServiceMappingsFactory = measurementService => {
|
||||
/**
|
||||
@ -17,17 +22,17 @@ const measurementServiceMappingsFactory = measurementService => {
|
||||
description,
|
||||
points,
|
||||
unit,
|
||||
sopInstanceUID,
|
||||
frameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID,
|
||||
} = measurement;
|
||||
|
||||
return {
|
||||
toolName: definition,
|
||||
measurementData: {
|
||||
sopInstanceUid: sopInstanceUID,
|
||||
frameOfReferenceUid: frameOfReferenceUID,
|
||||
seriesInstanceUid: referenceSeriesUID,
|
||||
sopInstanceUid: SOPInstanceUID,
|
||||
frameOfReferenceUID: FrameOfReferenceUID,
|
||||
SeriesInstanceUID: referenceSeriesUID,
|
||||
unit,
|
||||
text: label,
|
||||
description,
|
||||
@ -57,9 +62,9 @@ const measurementServiceMappingsFactory = measurementService => {
|
||||
}
|
||||
|
||||
const {
|
||||
sopInstanceUid,
|
||||
frameOfReferenceUid,
|
||||
seriesInstanceUid,
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
SeriesInstanceUID,
|
||||
} = _getAttributes(element);
|
||||
|
||||
const points = [];
|
||||
@ -67,13 +72,16 @@ const measurementServiceMappingsFactory = measurementService => {
|
||||
|
||||
return {
|
||||
id: measurementData._measurementServiceId,
|
||||
sopInstanceUID: sopInstanceUid,
|
||||
frameOfReferenceUID: frameOfReferenceUid,
|
||||
referenceSeriesUID: seriesInstanceUid,
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
label: measurementData.text,
|
||||
description: measurementData.description,
|
||||
unit: measurementData.unit,
|
||||
area: measurementData.cachedStats && measurementData.cachedStats.area, /* TODO: Add concept names instead (descriptor) */
|
||||
area:
|
||||
measurementData.cachedStats &&
|
||||
measurementData.cachedStats
|
||||
.area /* TODO: Add concept names instead (descriptor) */,
|
||||
type: _getValueTypeFromToolType(tool),
|
||||
points: _getPointsFromHandles(measurementData.handles),
|
||||
};
|
||||
@ -82,13 +90,13 @@ const measurementServiceMappingsFactory = measurementService => {
|
||||
const _getAttributes = element => {
|
||||
const enabledElement = cornerstone.getEnabledElement(element);
|
||||
const imageId = enabledElement.image.imageId;
|
||||
const sopInstance = cornerstone.metaData.get('instance', imageId);
|
||||
const sopInstanceUid = sopInstance.sopInstanceUid;
|
||||
const frameOfReferenceUid = sopInstance.frameOfReferenceUID;
|
||||
const series = cornerstone.metaData.get('series', imageId);
|
||||
const seriesInstanceUid = series.seriesInstanceUid;
|
||||
const instance = cornerstone.metaData.get('instance', imageId);
|
||||
|
||||
return { sopInstanceUid, frameOfReferenceUid, seriesInstanceUid };
|
||||
return {
|
||||
SOPInstanceUID: instance.SOPInstanceUID,
|
||||
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||
};
|
||||
};
|
||||
|
||||
const _getValueTypeFromToolType = toolType => {
|
||||
|
||||
@ -8,9 +8,9 @@ jest.mock('cornerstone-core', () => ({
|
||||
metaData: {
|
||||
...jest.requireActual('cornerstone-core').metaData,
|
||||
get: () => ({
|
||||
sopInstanceUid: "123",
|
||||
frameOfReferenceUID: "123",
|
||||
seriesInstanceUid: "123",
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
@ -42,8 +42,8 @@ describe('measurementServiceMappings.js', () => {
|
||||
measurementData: {
|
||||
_measurementServiceId: 1,
|
||||
sopInstanceUid: '123',
|
||||
frameOfReferenceUid: '123',
|
||||
seriesInstanceUid: '123',
|
||||
frameOfReferenceUID: '123',
|
||||
SeriesInstanceUID: '123',
|
||||
handles,
|
||||
text: 'Test',
|
||||
description: 'Test',
|
||||
@ -52,8 +52,8 @@ describe('measurementServiceMappings.js', () => {
|
||||
};
|
||||
measurement = {
|
||||
id: 1,
|
||||
sopInstanceUID: '123',
|
||||
frameOfReferenceUID: '123',
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '123',
|
||||
referenceSeriesUID: '123',
|
||||
label: 'Test',
|
||||
description: 'Test',
|
||||
@ -66,7 +66,10 @@ describe('measurementServiceMappings.js', () => {
|
||||
|
||||
describe('toAnnotation()', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@ -11,26 +11,26 @@ const SOP_CLASS_UIDS = {
|
||||
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
|
||||
};
|
||||
|
||||
const sopClassUids = Object.values(SOP_CLASS_UIDS);
|
||||
const sopClassUIDs = Object.values(SOP_CLASS_UIDS);
|
||||
|
||||
// TODO: Handle the case where there is more than one SOP Class Handler for the
|
||||
// same SOP Class
|
||||
const OHIFDicomHtmlSopClassHandler = {
|
||||
id: 'OHIFDicomHtmlSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
modality: 'SR',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@ -8,7 +8,7 @@ const SOP_CLASS_UIDS = {
|
||||
|
||||
const DicomMicroscopySopClassHandler = {
|
||||
id: 'DicomMicroscopySopClassHandlerPlugin',
|
||||
sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
||||
sopClassUIDs: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
@ -16,12 +16,12 @@ const DicomMicroscopySopClassHandler = {
|
||||
|
||||
return {
|
||||
plugin: 'microscopy',
|
||||
modality: 'SM',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'SM',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
dicomWebClient,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@ -25,8 +25,8 @@ class DicomMicroscopyViewport extends Component {
|
||||
const dicomWebClient = displaySet.dicomWebClient;
|
||||
|
||||
const searchInstanceOptions = {
|
||||
studyInstanceUID: displaySet.studyInstanceUid,
|
||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
||||
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||
};
|
||||
|
||||
dicomWebClient
|
||||
@ -34,18 +34,18 @@ class DicomMicroscopyViewport extends Component {
|
||||
.then(instances => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < instances.length; i++) {
|
||||
const sopInstanceUID = instances[i]['00080018']['Value'][0];
|
||||
const sopInstanceUid = instances[i]['00080018']['Value'][0];
|
||||
const retrieveInstanceOptions = {
|
||||
studyInstanceUID: displaySet.studyInstanceUid,
|
||||
seriesInstanceUID: displaySet.seriesInstanceUid,
|
||||
sopInstanceUID,
|
||||
studyInstanceUID: displaySet.StudyInstanceUID,
|
||||
seriesInstanceUID: displaySet.SeriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
};
|
||||
|
||||
const promise = dicomWebClient
|
||||
.retrieveInstanceMetadata(retrieveInstanceOptions)
|
||||
.then(metadata => {
|
||||
const imageType = metadata[0]['00080008']['Value'];
|
||||
if (imageType[2] === 'VOLUME') {
|
||||
const ImageType = metadata[0]['00080008']['Value'];
|
||||
if (ImageType[2] === 'VOLUME') {
|
||||
return metadata[0];
|
||||
}
|
||||
});
|
||||
@ -56,13 +56,15 @@ class DicomMicroscopyViewport extends Component {
|
||||
.then(async metadata => {
|
||||
metadata = metadata.filter(m => m);
|
||||
|
||||
const { api } = await import(/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer');
|
||||
const { api } = await import(
|
||||
/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'
|
||||
);
|
||||
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
|
||||
|
||||
this.viewer = new microscopyViewer({
|
||||
client: dicomWebClient,
|
||||
metadata,
|
||||
retrieveRendered: false
|
||||
retrieveRendered: false,
|
||||
});
|
||||
|
||||
this.viewer.render({ container });
|
||||
|
||||
@ -101,9 +101,9 @@ class DicomPDFViewport extends Component {
|
||||
let pdfByteArray = byteArray;
|
||||
|
||||
if (dataSet) {
|
||||
const sopClassUid = dataSet.string('x00080016');
|
||||
const SOPClassUID = dataSet.string('x00080016');
|
||||
|
||||
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||
if (SOPClassUID !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
|
||||
throw new Error('This is not a DICOM-encapsulated PDF');
|
||||
}
|
||||
|
||||
|
||||
@ -8,19 +8,19 @@ const SOP_CLASS_UIDS = {
|
||||
const OHIFDicomPDFSopClassHandler = {
|
||||
id: 'OHIFDicomPDFSopClassHandlerPlugin',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUids: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
||||
sopClassUIDs: [SOP_CLASS_UIDS.ENCAPSULATED_PDF],
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
return {
|
||||
plugin: 'pdf',
|
||||
modality: 'DOC',
|
||||
displaySetInstanceUid: utils.guid(),
|
||||
Modality: 'DOC',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
wadoRoot: study.getData().wadoRoot,
|
||||
wadoUri: instance.getData().wadouri,
|
||||
sopInstanceUid: instance.getSOPInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
authorizationHeaders: authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@ -33,6 +33,7 @@ class OHIFDicomPDFViewport extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { displaySet, studies } = this.props.viewportData;
|
||||
|
||||
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
|
||||
data => this.setState({ byteArray: new Uint8Array(data) }),
|
||||
error => {
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"@ohif/ui": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dcmjs": "^0.8.2",
|
||||
"dcmjs": "^0.10.1",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@ -54,7 +54,7 @@
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.5.2",
|
||||
"@ohif/ui": "^1.3.2",
|
||||
"cornerstone-tools": "^4.12.0",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"gh-pages": "^2.0.1",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { getImageData, loadImageData } from 'react-vtkjs-viewport';
|
||||
|
||||
import ConnectedVTKViewport from './ConnectedVTKViewport';
|
||||
import LoadingIndicator from './LoadingIndicator.js';
|
||||
import OHIF from '@ohif/core';
|
||||
@ -13,15 +12,7 @@ import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
|
||||
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
// Metadata configuration
|
||||
const metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
cornerstone.metaData.addProvider(
|
||||
metadataProvider.provider.bind(metadataProvider)
|
||||
);
|
||||
|
||||
StackManager.setMetadataProvider(metadataProvider);
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
const SOP_CLASSES = {
|
||||
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
@ -68,10 +59,10 @@ class OHIFVTKViewport extends Component {
|
||||
viewportData: PropTypes.shape({
|
||||
studies: PropTypes.array,
|
||||
displaySet: PropTypes.shape({
|
||||
studyInstanceUid: PropTypes.string,
|
||||
displaySetInstanceUid: PropTypes.string,
|
||||
sopClassUids: PropTypes.arrayOf(PropTypes.string),
|
||||
sopInstanceUid: PropTypes.string,
|
||||
StudyInstanceUID: PropTypes.string,
|
||||
displaySetInstanceUID: PropTypes.string,
|
||||
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
||||
SOPInstanceUID: PropTypes.string,
|
||||
frameIndex: PropTypes.number,
|
||||
}),
|
||||
}),
|
||||
@ -97,18 +88,18 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
static getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) {
|
||||
// Create shortcut to displaySet
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@ -119,17 +110,14 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
if (frameIndex !== undefined) {
|
||||
stack.currentImageIdIndex = frameIndex;
|
||||
} else if (sopInstanceUid) {
|
||||
} else if (SOPInstanceUID) {
|
||||
const index = stack.imageIds.findIndex(imageId => {
|
||||
const sopCommonModule = cornerstone.metaData.get(
|
||||
'sopCommonModule',
|
||||
const imageIdSOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
imageId
|
||||
);
|
||||
if (!sopCommonModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
return sopCommonModule.sopInstanceUID === sopInstanceUid;
|
||||
return imageIdSOPInstanceUID === SOPInstanceUID;
|
||||
});
|
||||
|
||||
if (index > -1) {
|
||||
@ -144,31 +132,31 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
getViewportData = (
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
let imageDataObject;
|
||||
let labelmapDataObject;
|
||||
|
||||
switch (sopClassUid) {
|
||||
switch (SOPClassUID) {
|
||||
case SOP_CLASSES.SEGMENTATION_STORAGE:
|
||||
throw new Error('Not yet implemented');
|
||||
/*
|
||||
const data = handleSegmentationStorage(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
imageDataObject = data.referenceDataObject;
|
||||
@ -182,7 +170,7 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
*/
|
||||
default:
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUid);
|
||||
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
|
||||
return imageDataObject;
|
||||
}
|
||||
@ -194,25 +182,31 @@ class OHIFVTKViewport extends Component {
|
||||
* @param {object} imageDataObject
|
||||
* @param {object} imageDataObject.vtkImageData
|
||||
* @param {object} imageDataObject.imageMetaData0
|
||||
* @param {number} [imageDataObject.imageMetaData0.windowWidth] - The volume's initial windowWidth
|
||||
* @param {number} [imageDataObject.imageMetaData0.windowCenter] - The volume's initial windowCenter
|
||||
* @param {string} imageDataObject.imageMetaData0.modality - CT, MR, PT, etc
|
||||
* @param {string} displaySetInstanceUid
|
||||
* @param {number} [imageDataObject.imageMetaData0.WindowWidth] - The volume's initial WindowWidth
|
||||
* @param {number} [imageDataObject.imageMetaData0.WindowCenter] - The volume's initial WindowCenter
|
||||
* @param {string} imageDataObject.imageMetaData0.Modality - CT, MR, PT, etc
|
||||
* @param {string} displaySetInstanceUID
|
||||
* @returns vtkVolumeActor
|
||||
* @memberof OHIFVTKViewport
|
||||
*/
|
||||
getOrCreateVolume(imageDataObject, displaySetInstanceUid) {
|
||||
if (volumeCache[displaySetInstanceUid]) {
|
||||
return volumeCache[displaySetInstanceUid];
|
||||
getOrCreateVolume(imageDataObject, displaySetInstanceUID) {
|
||||
if (volumeCache[displaySetInstanceUID]) {
|
||||
return volumeCache[displaySetInstanceUID];
|
||||
}
|
||||
|
||||
const { vtkImageData, imageMetaData0 } = imageDataObject;
|
||||
const { windowWidth, windowCenter, modality } = imageMetaData0;
|
||||
// TODO -> Should update react-vtkjs-viewport and react-cornerstone-viewports
|
||||
// internals to use naturalized DICOM JSON names.
|
||||
const {
|
||||
windowWidth: WindowWidth,
|
||||
windowCenter: WindowCenter,
|
||||
modality: Modality,
|
||||
} = imageMetaData0;
|
||||
|
||||
const { lower, upper } = _getRangeFromWindowLevels(
|
||||
windowWidth,
|
||||
windowCenter,
|
||||
modality
|
||||
WindowWidth,
|
||||
WindowCenter,
|
||||
Modality
|
||||
);
|
||||
const volumeActor = vtkVolume.newInstance();
|
||||
const volumeMapper = vtkVolumeMapper.newInstance();
|
||||
@ -236,7 +230,7 @@ class OHIFVTKViewport extends Component {
|
||||
// TODO: maybe we should auto adjust samples to 1000.
|
||||
volumeMapper.setMaximumSamplesPerRay(4000);
|
||||
|
||||
volumeCache[displaySetInstanceUid] = volumeActor;
|
||||
volumeCache[displaySetInstanceUID] = volumeActor;
|
||||
|
||||
return volumeActor;
|
||||
}
|
||||
@ -244,26 +238,26 @@ class OHIFVTKViewport extends Component {
|
||||
setStateFromProps() {
|
||||
const { studies, displaySet } = this.props.viewportData;
|
||||
const {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUids,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
sopClassUIDs,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
} = displaySet;
|
||||
|
||||
if (sopClassUids.length > 1) {
|
||||
if (sopClassUIDs.length > 1) {
|
||||
console.warn(
|
||||
'More than one SOPClassUid in the same series is not yet supported.'
|
||||
'More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs[0];
|
||||
const imageDataObject = this.getViewportData(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopClassUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
@ -277,7 +271,7 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
const volumeActor = this.getOrCreateVolume(
|
||||
imageDataObject,
|
||||
displaySetInstanceUid
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
this.setState({
|
||||
@ -317,9 +311,9 @@ class OHIFVTKViewport extends Component {
|
||||
const prevDisplaySet = prevProps.viewportData.displaySet;
|
||||
|
||||
if (
|
||||
displaySet.displaySetInstanceUid !==
|
||||
prevDisplaySet.displaySetInstanceUid ||
|
||||
displaySet.sopInstanceUid !== prevDisplaySet.sopInstanceUid ||
|
||||
displaySet.displaySetInstanceUID !==
|
||||
prevDisplaySet.displaySetInstanceUID ||
|
||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||
) {
|
||||
this.setStateFromProps();
|
||||
@ -331,7 +325,7 @@ class OHIFVTKViewport extends Component {
|
||||
|
||||
const { isLoading, insertPixelDataPromises } = imageDataObject;
|
||||
|
||||
const numberOfFrames = insertPixelDataPromises.length;
|
||||
const NumberOfFrames = insertPixelDataPromises.length;
|
||||
|
||||
if (!isLoading) {
|
||||
this.setState({ isLoaded: true });
|
||||
@ -341,7 +335,7 @@ class OHIFVTKViewport extends Component {
|
||||
insertPixelDataPromises.forEach(promise => {
|
||||
promise.then(numberProcessed => {
|
||||
const percentComplete = Math.floor(
|
||||
(numberProcessed * 100) / numberOfFrames
|
||||
(numberProcessed * 100) / NumberOfFrames
|
||||
);
|
||||
|
||||
if (percentComplete !== this.state.percentComplete) {
|
||||
@ -373,8 +367,6 @@ class OHIFVTKViewport extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
const style = { width: '100%', height: '100%', position: 'relative' };
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={style}>
|
||||
@ -409,21 +401,21 @@ class OHIFVTKViewport extends Component {
|
||||
* @private
|
||||
* @param {number} [width] - the width of our window
|
||||
* @param {number} [center] - the center of our window
|
||||
* @param {string} [modality] - 'PT', 'CT', etc.
|
||||
* @param {string} [Modality] - 'PT', 'CT', etc.
|
||||
* @returns { lower, upper } - range
|
||||
*/
|
||||
function _getRangeFromWindowLevels(width, center, modality = undefined) {
|
||||
function _getRangeFromWindowLevels(width, center, Modality = undefined) {
|
||||
// For PET just set the range to 0-5 SUV
|
||||
if (Modality === 'PT') {
|
||||
return { lower: 0, upper: 5 };
|
||||
}
|
||||
|
||||
const levelsAreNotNumbers = isNaN(center) || isNaN(width);
|
||||
|
||||
if (levelsAreNotNumbers) {
|
||||
return { lower: 0, upper: 512 };
|
||||
}
|
||||
|
||||
// For PET just set the range to 0-5 SUV
|
||||
if (modality === 'PT') {
|
||||
return { lower: 0, upper: 5 };
|
||||
}
|
||||
|
||||
return {
|
||||
lower: center - width / 2.0,
|
||||
upper: center + width / 2.0,
|
||||
|
||||
@ -19,8 +19,8 @@ const VTKViewport = props => {
|
||||
useEffect(() => {
|
||||
const handleScrollEvent = evt => {
|
||||
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
|
||||
const viewportUid = vtkViewportApiReference.uid;
|
||||
const viewportWasScrolled = viewportUid === evt.detail.uid;
|
||||
const viewportUID = vtkViewportApiReference.uid;
|
||||
const viewportWasScrolled = viewportUID === evt.detail.uid;
|
||||
|
||||
if (viewportWasScrolled) {
|
||||
setViewportActiveHandler();
|
||||
|
||||
@ -70,12 +70,9 @@ const commandsModule = ({ commandsManager }) => {
|
||||
if (cornerstoneElement) {
|
||||
const imageId = cornerstoneElement.image.imageId;
|
||||
|
||||
const { modality } = cornerstone.metaData.get(
|
||||
'generalSeriesModule',
|
||||
imageId
|
||||
);
|
||||
const Modality = cornerstone.metaData.get('Modality', imageId);
|
||||
|
||||
if (modality !== 'PT') {
|
||||
if (Modality !== 'PT') {
|
||||
const { windowWidth, windowCenter } = cornerstoneElement.viewport.voi;
|
||||
|
||||
return {
|
||||
|
||||
@ -1,42 +1,42 @@
|
||||
import * as dcmjs from "dcmjs";
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import OHIF from "@ohif/core";
|
||||
import { api } from "dicomweb-client";
|
||||
import OHIF from '@ohif/core';
|
||||
import { api } from 'dicomweb-client';
|
||||
|
||||
const { StackManager } = OHIF.utils;
|
||||
|
||||
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySet = study.displaySets.find(set => {
|
||||
return set.displaySetInstanceUid === displaySetInstanceUid;
|
||||
return set.displaySetInstanceUID === displaySetInstanceUID;
|
||||
});
|
||||
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
function getDisplaySetsBySeries(studies, studyInstanceUid, seriesInstanceUid) {
|
||||
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
return study.displaySets.filter(set => {
|
||||
return set.seriesInstanceUid === seriesInstanceUid;
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
}
|
||||
|
||||
function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
// Create shortcut to displaySet
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Get stack from Stack Manager
|
||||
@ -52,19 +52,19 @@ function getCornerstoneStack(studies, studyInstanceUid, displaySetInstanceUid) {
|
||||
function retrieveDicomData(
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUID,
|
||||
sopInstanceUid,
|
||||
wadoRoot
|
||||
) {
|
||||
const config = {
|
||||
url: wadoRoot,
|
||||
headers: DICOMWeb.getAuthorizationHeader()
|
||||
headers: DICOMWeb.getAuthorizationHeader(),
|
||||
};
|
||||
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
studyInstanceUID,
|
||||
seriesInstanceUID,
|
||||
sopInstanceUID
|
||||
sopInstanceUid,
|
||||
};
|
||||
|
||||
return dicomWeb.retrieveInstance(options);
|
||||
@ -72,30 +72,26 @@ function retrieveDicomData(
|
||||
|
||||
async function handleSegmentationStorage(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUid === studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
const displaySet = getDisplaySet(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
|
||||
// from google cloud
|
||||
const wadoRoot = displaySet.images[0].getData().wadoRoot;
|
||||
|
||||
const StudyInstanceUID = displaySet.images[0].getStudyInstanceUID();
|
||||
const SeriesInstanceUID = displaySet.images[0].getSeriesInstanceUID();
|
||||
const SOPInstanceUID = displaySet.images[0].getSOPInstanceUID();
|
||||
|
||||
const arrayBuffer = await retrieveDicomData(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
displaySet.images[0].getStudyInstanceUID(),
|
||||
displaySet.images[0].getSeriesInstanceUID(),
|
||||
displaySet.images[0].getSOPInstanceUID(),
|
||||
wadoRoot
|
||||
);
|
||||
|
||||
@ -108,16 +104,16 @@ async function handleSegmentationStorage(
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
|
||||
const seriesInstanceUid = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
|
||||
const displaySets = getDisplaySetsBySeries(
|
||||
studies,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
"More than one display set with the same seriesInstanceUid. This is not supported yet..."
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
}
|
||||
|
||||
@ -125,7 +121,7 @@ async function handleSegmentationStorage(
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
|
||||
|
||||
if (!results) {
|
||||
throw new Error("Fractional segmentations are not supported");
|
||||
throw new Error('Fractional segmentations are not supported');
|
||||
}
|
||||
|
||||
const cachedStack = StackManager.findOrCreateStack(
|
||||
@ -137,13 +133,13 @@ async function handleSegmentationStorage(
|
||||
|
||||
return {
|
||||
referenceDataObject,
|
||||
labelmapDataObject
|
||||
labelmapDataObject,
|
||||
};
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
stack
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -13,21 +13,21 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
|
||||
return false;
|
||||
};
|
||||
|
||||
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
||||
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
|
||||
activeViewportIndex
|
||||
];
|
||||
|
||||
const studies = studyMetadataManager.all();
|
||||
|
||||
const study = studies.find(
|
||||
study => study.studyInstanceUID === studyInstanceUid
|
||||
study => study.studyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
if (!study) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid);
|
||||
const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
|
||||
|
||||
if (!displaySet) {
|
||||
return false;
|
||||
|
||||
@ -66,8 +66,8 @@ import { studies } from '@ohif/core';
|
||||
|
||||
const studiesMetadata = await studies.retrieveStudiesMetadata(
|
||||
server, // Object
|
||||
studyInstanceUids, // Array
|
||||
seriesInstanceUids // Array (optional)
|
||||
studyInstanceUIDs, // Array
|
||||
seriesInstanceUIDs // Array (optional)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.12.0",
|
||||
"cornerstone-tools": "4.12.0",
|
||||
"cornerstone-wado-image-loader": "^3.0.0",
|
||||
"dicom-parser": "^1.8.3"
|
||||
},
|
||||
|
||||
@ -31,6 +31,7 @@ const retrieveMeasurements = server => {
|
||||
|
||||
const serverUrl = server.wadoRoot;
|
||||
const studies = utils.studyMetadataManager.all();
|
||||
|
||||
const latestSeries = findMostRecentStructuredReport(studies);
|
||||
|
||||
if (!latestSeries) return Promise.resolve({});
|
||||
@ -57,17 +58,17 @@ const storeMeasurements = async (measurementData, filter, server) => {
|
||||
const serverUrl = server.wadoRoot;
|
||||
const firstMeasurementKey = Object.keys(measurementData)[0];
|
||||
const firstMeasurement = measurementData[firstMeasurementKey][0];
|
||||
const studyInstanceUid =
|
||||
firstMeasurement && firstMeasurement.studyInstanceUid;
|
||||
const StudyInstanceUID =
|
||||
firstMeasurement && firstMeasurement.StudyInstanceUID;
|
||||
|
||||
try {
|
||||
await stowSRFromMeasurements(measurementData, serverUrl);
|
||||
if (studyInstanceUid) {
|
||||
studies.deleteStudyMetadataPromise(studyInstanceUid);
|
||||
if (StudyInstanceUID) {
|
||||
studies.deleteStudyMetadataPromise(StudyInstanceUID);
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Measurements were saved with success',
|
||||
message: 'Measurements saved successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as dcmjs from 'dcmjs';
|
||||
|
||||
import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid';
|
||||
import findInstanceMetadataBySopInstanceUID from './utils/findInstanceMetadataBySopInstanceUid';
|
||||
|
||||
/**
|
||||
* Function to parse the part10 array buffer that comes from a DICOM Structured report into measurementData
|
||||
@ -13,6 +13,7 @@ import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBy
|
||||
*/
|
||||
const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
// Get the dicom data as an Object
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(part10SRArrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
@ -30,17 +31,18 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
measurementData[toolName] = [];
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
const instanceMetadata = findInstanceMetadataBySopInstanceUid(
|
||||
const instanceMetadata = findInstanceMetadataBySopInstanceUID(
|
||||
displaySets,
|
||||
measurement.sopInstanceUid
|
||||
);
|
||||
|
||||
const { _study: study, _series: series } = instanceMetadata;
|
||||
const { studyInstanceUid, patientId } = study;
|
||||
const { seriesInstanceUid } = series;
|
||||
const { StudyInstanceUID, PatientID } = study;
|
||||
const { SeriesInstanceUID } = series;
|
||||
const { sopInstanceUid, frameIndex } = measurement;
|
||||
const imagePath = getImagePath(
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
sopInstanceUid,
|
||||
frameIndex
|
||||
);
|
||||
@ -56,9 +58,10 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
const toolData = Object.assign({}, measurement, {
|
||||
imageId,
|
||||
imagePath,
|
||||
seriesInstanceUid,
|
||||
studyInstanceUid,
|
||||
patientId,
|
||||
SOPInstanceUID: sopInstanceUid,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
PatientID,
|
||||
measurementNumber: ++measurementNumber,
|
||||
timepointId: currentTimepointId,
|
||||
toolType: toolName,
|
||||
@ -75,19 +78,19 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
||||
/**
|
||||
* Function to create imagePath with all imageData related
|
||||
*
|
||||
* @param {string} studyInstanceUid
|
||||
* @param {string} seriesInstanceUid
|
||||
* @param {string} sopInstanceUid
|
||||
* @param {string} StudyInstanceUID
|
||||
* @param {string} SeriesInstanceUID
|
||||
* @param {string} SOPInstanceUID
|
||||
* @param {string} frameIndex
|
||||
* @returns
|
||||
*/
|
||||
const getImagePath = (
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join(
|
||||
return [StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID, frameIndex].join(
|
||||
'_'
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,17 +2,18 @@
|
||||
* Should Find the requested instance metadata into the displaySets and return
|
||||
*
|
||||
* @param {Array} displaySets - List of displaySets
|
||||
* @param {string} sopInstanceUid - sopInstanceUID to look for
|
||||
* @param {string} SOPInstanceUID - sopInstanceUID to look for
|
||||
* @returns {Object} - instance metadata found
|
||||
*/
|
||||
const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
||||
const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => {
|
||||
let instanceFound;
|
||||
|
||||
displaySets.find(displaySet => {
|
||||
if (!displaySet.images) return false;
|
||||
|
||||
instanceFound = displaySet.images.find(
|
||||
instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid
|
||||
instanceMetadata =>
|
||||
instanceMetadata.getSOPInstanceUID() === SOPInstanceUID
|
||||
);
|
||||
|
||||
return !!instanceFound;
|
||||
@ -21,4 +22,4 @@ const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
||||
return instanceFound;
|
||||
};
|
||||
|
||||
export default findInstanceMetadataBySopInstanceUid;
|
||||
export default findInstanceMetadataBySopInstanceUID;
|
||||
|
||||
@ -37,9 +37,9 @@ const isStructuredReportSeries = series => {
|
||||
];
|
||||
|
||||
const firstInstance = series.getFirstInstance();
|
||||
const sopClassUid = firstInstance._instance.sopClassUid;
|
||||
const SOPClassUID = firstInstance.getData().metadata.SOPClassUID;
|
||||
|
||||
return supportedSopClassUIDs.includes(sopClassUid);
|
||||
return supportedSopClassUIDs.includes(SOPClassUID);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -51,9 +51,9 @@ const isStructuredReportSeries = series => {
|
||||
*/
|
||||
const compareSeriesDate = (series1, series2) => {
|
||||
return (
|
||||
series1._data.seriesDate > series2._data.seriesDate ||
|
||||
(series1._data.seriesDate === series2._data.seriesDate &&
|
||||
series1._data.seriesTime > series2._data.seriesTime)
|
||||
series1._data.SeriesDate > series2._data.SeriesDate ||
|
||||
(series1._data.SeriesDate === series2._data.SeriesDate &&
|
||||
series1._data.SeriesTime > series2._data.SeriesTime)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
export default function getModalities(modality, modalitiesInStudy) {
|
||||
if (!modality && !modalitiesInStudy) {
|
||||
export default function getModalities(Modality, ModalitiesInStudy) {
|
||||
if (!Modality && !ModalitiesInStudy) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const modalities = modality || {
|
||||
const modalities = Modality || {
|
||||
vr: 'CS',
|
||||
Value: [],
|
||||
};
|
||||
|
||||
if (modalitiesInStudy) {
|
||||
if (modalities.vr && modalities.vr === modalitiesInStudy.vr) {
|
||||
for (let i = 0; i < modalitiesInStudy.Value.length; i++) {
|
||||
const value = modalitiesInStudy.Value[i];
|
||||
if (ModalitiesInStudy) {
|
||||
if (modalities.vr && modalities.vr === ModalitiesInStudy.vr) {
|
||||
for (let i = 0; i < ModalitiesInStudy.Value.length; i++) {
|
||||
const value = ModalitiesInStudy.Value[i];
|
||||
if (modalities.Value.indexOf(value) === -1) {
|
||||
modalities.Value.push(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return modalitiesInStudy;
|
||||
return ModalitiesInStudy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,74 +1,74 @@
|
||||
import getModalities from './getModalities';
|
||||
|
||||
describe('getModalities', () => {
|
||||
test('should return an empty object when modality and modalitiesInStudy are not present', () => {
|
||||
const modality = null;
|
||||
const modalitiesInStudy = null;
|
||||
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||
const Modality = null;
|
||||
const ModalitiesInStudy = null;
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual({});
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual({});
|
||||
});
|
||||
|
||||
test('should return an empty object when modality and modalitiesInStudy are not present', () => {
|
||||
const modality = null;
|
||||
const modalitiesInStudy = null;
|
||||
test('should return an empty object when Modality and ModalitiesInStudy are not present', () => {
|
||||
const Modality = null;
|
||||
const ModalitiesInStudy = null;
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual({});
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual({});
|
||||
});
|
||||
|
||||
test('should return modalities in Study when modality is not defined', () => {
|
||||
const modality = null;
|
||||
test('should return modalities in Study when Modality is not defined', () => {
|
||||
const Modality = null;
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['MOCKED_VALUE'],
|
||||
vr: 'MOCKED_VALUE',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
||||
modalitiesInStudy
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||
ModalitiesInStudy
|
||||
);
|
||||
});
|
||||
|
||||
test('should return only the modalitues that exists in modalitiesInStudy', () => {
|
||||
const modality = {
|
||||
test('should return only the modalitues that exists in ModalitiesInStudy', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['DESIRED_VALUE', 'NOT_DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(modality);
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality);
|
||||
});
|
||||
|
||||
test('should return the seek modality when the desired modality does not exist in modalitiesInStudy', () => {
|
||||
const modality = {
|
||||
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['NOT_DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(modality);
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(Modality);
|
||||
});
|
||||
|
||||
test('should return the seek modality when the desired modality does not exist in modalitiesInStudy VR', () => {
|
||||
const modality = {
|
||||
test('should return the seek Modality when the desired Modality does not exist in ModalitiesInStudy VR', () => {
|
||||
const Modality = {
|
||||
Value: ['DESIRED_VALUE'],
|
||||
vr: 'DESIRED_VR',
|
||||
};
|
||||
|
||||
const modalitiesInStudy = {
|
||||
const ModalitiesInStudy = {
|
||||
Value: ['NOT_DESIRED_VALUE'],
|
||||
vr: 'ANOTHER_VR',
|
||||
};
|
||||
|
||||
expect(getModalities(modality, modalitiesInStudy)).toEqual(
|
||||
modalitiesInStudy
|
||||
expect(getModalities(Modality, ModalitiesInStudy)).toEqual(
|
||||
ModalitiesInStudy
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,412 +1,508 @@
|
||||
import { parsingUtils } from '../lib/parsingUtils';
|
||||
|
||||
const FUNCTION = 'function';
|
||||
import * as dcmjs from 'dcmjs';
|
||||
import queryString from 'query-string';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import { getFallbackTagFromInstance } from '../utils/metadataProvider/metadataProviderFallbackTags';
|
||||
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
||||
import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData';
|
||||
|
||||
class MetadataProvider {
|
||||
constructor() {
|
||||
// Define the main "metadataLookup" private property as an immutable property.
|
||||
Object.defineProperty(this, 'metadataLookup', {
|
||||
Object.defineProperty(this, 'studies', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: new Map(),
|
||||
});
|
||||
|
||||
// Local reference to provider function bound to current instance.
|
||||
Object.defineProperty(this, '_provider', {
|
||||
Object.defineProperty(this, 'imageIdToUIDs', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null,
|
||||
writable: false,
|
||||
value: new Map(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cornerstone Metadata provider to store image meta data
|
||||
* Data from instances, series, and studies are associated with
|
||||
* imageIds to facilitate usage of this information by Cornerstone's Tools
|
||||
*
|
||||
* e.g. the imagePlane metadata object contains instance information about
|
||||
* row/column pixel spacing, patient position, and patient orientation. It
|
||||
* is used in CornerstoneTools to position reference lines and orientation markers.
|
||||
*
|
||||
* @param {String} imageId The Cornerstone ImageId
|
||||
* @param {Object} data An object containing instance, series, and study metadata
|
||||
*/
|
||||
addMetadata(imageId, data) {
|
||||
const instanceMetadata = data.instance;
|
||||
const seriesMetadata = data.series;
|
||||
const studyMetadata = data.study;
|
||||
const numImages = data.numImages;
|
||||
const metadata = {};
|
||||
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
|
||||
let dicomJSONDataset;
|
||||
|
||||
metadata.frameNumber = data.frameNumber;
|
||||
// If Arraybuffer, parse to DICOMJSON before naturalizing.
|
||||
if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {
|
||||
const dicomData = DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer);
|
||||
|
||||
metadata.study = {
|
||||
accessionNumber: studyMetadata.accessionNumber,
|
||||
patientId: studyMetadata.patientId,
|
||||
studyInstanceUid: studyMetadata.studyInstanceUid,
|
||||
studyDate: studyMetadata.studyDate,
|
||||
studyTime: studyMetadata.studyTime,
|
||||
studyDescription: studyMetadata.studyDescription,
|
||||
institutionName: studyMetadata.institutionName,
|
||||
patientHistory: studyMetadata.patientHistory,
|
||||
};
|
||||
|
||||
metadata.series = {
|
||||
seriesDescription: seriesMetadata.seriesDescription,
|
||||
seriesNumber: seriesMetadata.seriesNumber,
|
||||
seriesDate: seriesMetadata.seriesDate,
|
||||
seriesTime: seriesMetadata.seriesTime,
|
||||
modality: seriesMetadata.modality,
|
||||
seriesInstanceUid: seriesMetadata.seriesInstanceUid,
|
||||
numImages: numImages,
|
||||
};
|
||||
|
||||
metadata.instance = instanceMetadata;
|
||||
|
||||
metadata.patient = {
|
||||
name: studyMetadata.patientName,
|
||||
id: studyMetadata.patientId,
|
||||
birthDate: studyMetadata.patientBirthDate,
|
||||
sex: studyMetadata.patientSex,
|
||||
age: studyMetadata.patientAge,
|
||||
};
|
||||
|
||||
// If there is sufficient information, populate
|
||||
// the imagePlane object for easier use in the Viewer
|
||||
metadata.imagePlane = this.getImagePlane(instanceMetadata);
|
||||
|
||||
// Add the metadata to the imageId lookup object
|
||||
this.metadataLookup.set(imageId, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the metadata for the given imageId
|
||||
* @param {String} imageId The Cornerstone ImageId
|
||||
* @returns image metadata
|
||||
*/
|
||||
getMetadata(imageId) {
|
||||
return this.metadataLookup.get(imageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of metadata to the Cornerstone metadata provider given a specific
|
||||
* imageId, type, and dataset
|
||||
*
|
||||
* @param imageId
|
||||
* @param type (e.g. series, instance, tagDisplay)
|
||||
* @param data
|
||||
*/
|
||||
addSpecificMetadata(imageId, type, data) {
|
||||
const metadata = {};
|
||||
metadata[type] = data;
|
||||
|
||||
const oldMetadata = this.metadataLookup.get(imageId);
|
||||
this.metadataLookup.set(imageId, Object.assign(oldMetadata, metadata));
|
||||
}
|
||||
|
||||
getFromImage(image, type, tag, attrName, defaultValue) {
|
||||
let value;
|
||||
|
||||
if (image.data) {
|
||||
value = this.getFromDataSet(image.data, type, tag);
|
||||
dicomJSONDataset = dicomData.dict;
|
||||
} else {
|
||||
value = image.instance[attrName];
|
||||
dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;
|
||||
}
|
||||
|
||||
return value === null ? defaultValue : value;
|
||||
}
|
||||
// Check if dataset is already naturalized.
|
||||
|
||||
getFromDataSet(dataSet, type, tag) {
|
||||
if (!dataSet) {
|
||||
return;
|
||||
}
|
||||
let naturalizedDataset;
|
||||
|
||||
const fn = dataSet[type];
|
||||
if (!fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn.call(dataSet, tag);
|
||||
}
|
||||
|
||||
getFrameIncrementPointer(image) {
|
||||
const dataSet = image.data;
|
||||
let frameInstancePointer = '';
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
const frameInstancePointerNames = {
|
||||
x00181063: 'frameTime',
|
||||
x00181065: 'frameTimeVector',
|
||||
};
|
||||
|
||||
// (0028,0009) = Frame Increment Pointer
|
||||
const frameInstancePointerTag = parsingUtils.attributeTag(
|
||||
dataSet,
|
||||
'x00280009'
|
||||
if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {
|
||||
naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomJSONDataset
|
||||
);
|
||||
frameInstancePointer = frameInstancePointerNames[frameInstancePointerTag];
|
||||
} else {
|
||||
frameInstancePointer = image.instance.frameIncrementPointer;
|
||||
naturalizedDataset = dicomJSONDataset;
|
||||
}
|
||||
|
||||
return frameInstancePointer || '';
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = naturalizedDataset;
|
||||
|
||||
const study = this._getAndCacheStudy(StudyInstanceUID);
|
||||
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
|
||||
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
|
||||
|
||||
Object.assign(instance, naturalizedDataset);
|
||||
|
||||
if (options.server) {
|
||||
await this._checkBulkDataAndInlineBinaries(instance, options.server);
|
||||
}
|
||||
|
||||
getFrameTimeVector(image) {
|
||||
const dataSet = image.data;
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
||||
return parsingUtils.floatArray(dataSet, 'x00181065');
|
||||
return instance;
|
||||
}
|
||||
|
||||
return image.instance.frameTimeVector;
|
||||
addImageIdToUIDs(imageId, uids) {
|
||||
// This method is a fallback for when you don't have WADO-URI or WADO-RS.
|
||||
// You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here.
|
||||
// An example would be dicom hosted at some random site.
|
||||
|
||||
this.imageIdToUIDs.set(imageId, uids);
|
||||
}
|
||||
|
||||
getFrameTime(image) {
|
||||
const dataSet = image.data;
|
||||
_getAndCacheStudy(StudyInstanceUID) {
|
||||
const studies = this.studies;
|
||||
|
||||
if (parsingUtils.isValidDataSet(dataSet)) {
|
||||
// Frame Increment Pointer points to Frame Time (0018,1063) field or is not defined (for addtional flexibility).
|
||||
// Yet another value is possible for this field (5200,9230 for Multi-frame Functional Groups)
|
||||
// but that case is currently not supported.
|
||||
return dataSet.floatString('x00181063', -1);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the related metadata for missing fields given a specified image
|
||||
*
|
||||
* @param image
|
||||
*/
|
||||
updateMetadata(image) {
|
||||
const imageMetadata = this.metadataLookup.get(image.imageId);
|
||||
if (!imageMetadata) {
|
||||
return series;
|
||||
}
|
||||
|
||||
_getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
||||
let instance = series.instances.get(SOPInstanceUID);
|
||||
|
||||
if (!instance) {
|
||||
instance = {};
|
||||
series.instances.set(SOPInstanceUID, instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async _checkBulkDataAndInlineBinaries(instance, server) {
|
||||
await fetchOverlayData(instance, server);
|
||||
|
||||
if (instance.PhotometricInterpretation === 'PALETTE COLOR') {
|
||||
await fetchPaletteColorLookupTableData(instance, server);
|
||||
}
|
||||
}
|
||||
|
||||
_getInstance(imageId) {
|
||||
const uids = this._getUIDsFromImageID(imageId);
|
||||
|
||||
if (!uids) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageMetadata.patient.age =
|
||||
imageMetadata.patient.age ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00101010');
|
||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = uids;
|
||||
|
||||
imageMetadata.instance.rows = imageMetadata.instance.rows || image.rows;
|
||||
imageMetadata.instance.columns =
|
||||
imageMetadata.instance.columns || image.columns;
|
||||
|
||||
imageMetadata.instance.sopClassUid =
|
||||
imageMetadata.instance.sopClassUid ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00080016');
|
||||
imageMetadata.instance.sopInstanceUid =
|
||||
imageMetadata.instance.sopInstanceUid ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00080018');
|
||||
|
||||
imageMetadata.instance.pixelSpacing =
|
||||
imageMetadata.instance.pixelSpacing ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00280030');
|
||||
imageMetadata.instance.frameOfReferenceUID =
|
||||
imageMetadata.instance.frameOfReferenceUID ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200052');
|
||||
imageMetadata.instance.imageOrientationPatient =
|
||||
imageMetadata.instance.imageOrientationPatient ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200037');
|
||||
imageMetadata.instance.imagePositionPatient =
|
||||
imageMetadata.instance.imagePositionPatient ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00200032');
|
||||
|
||||
imageMetadata.instance.sliceThickness =
|
||||
imageMetadata.instance.sliceThickness ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00180050');
|
||||
imageMetadata.instance.sliceLocation =
|
||||
imageMetadata.instance.sliceLocation ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00201041');
|
||||
imageMetadata.instance.tablePosition =
|
||||
imageMetadata.instance.tablePosition ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00189327');
|
||||
imageMetadata.instance.spacingBetweenSlices =
|
||||
imageMetadata.instance.spacingBetweenSlices ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00180088');
|
||||
|
||||
imageMetadata.instance.lossyImageCompression =
|
||||
imageMetadata.instance.lossyImageCompression ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00282110');
|
||||
imageMetadata.instance.lossyImageCompressionRatio =
|
||||
imageMetadata.instance.lossyImageCompressionRatio ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00282112');
|
||||
|
||||
imageMetadata.instance.frameIncrementPointer =
|
||||
imageMetadata.instance.frameIncrementPointer ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00280009');
|
||||
imageMetadata.instance.frameTime =
|
||||
imageMetadata.instance.frameTime ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00181063');
|
||||
imageMetadata.instance.frameTimeVector =
|
||||
imageMetadata.instance.frameTimeVector ||
|
||||
this.getFromDataSet(image.data, 'string', 'x00181065');
|
||||
|
||||
if (
|
||||
(image.data || image.instance) &&
|
||||
!imageMetadata.instance.multiframeMetadata
|
||||
) {
|
||||
imageMetadata.instance.multiframeMetadata = this.getMultiframeModuleMetadata(
|
||||
image
|
||||
return this._getInstanceData(
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
imageMetadata.imagePlane =
|
||||
imageMetadata.imagePlane || this.getImagePlane(imageMetadata.instance);
|
||||
get(query, imageId, options = { fallback: false }) {
|
||||
const instance = this._getInstance(imageId);
|
||||
|
||||
if (query === INSTANCE) {
|
||||
return 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 this.getTagFromInstance(query, instance, options);
|
||||
}
|
||||
|
||||
getTag(query, imageId, options) {
|
||||
return this.get(query, imageId, options);
|
||||
}
|
||||
|
||||
getInstance(imageId) {
|
||||
return this.get(INSTANCE, imageId);
|
||||
}
|
||||
|
||||
getTagFromInstance(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance,
|
||||
options = { fallback: false }
|
||||
) {
|
||||
if (!instance) {
|
||||
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]);
|
||||
// If its a naturalized dcmjs tag present on the instance, return.
|
||||
if (instance[naturalizedTagOrWADOImageLoaderTag]) {
|
||||
return instance[naturalizedTagOrWADOImageLoaderTag];
|
||||
}
|
||||
|
||||
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]),
|
||||
],
|
||||
if (options.fallback) {
|
||||
// Perhaps the tag has fallbacks?
|
||||
const fallbackTag = getFallbackTagFromInstance(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance
|
||||
);
|
||||
|
||||
if (fallbackTag) {
|
||||
return fallbackTag;
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe its a legacy CornerstoneWADOImageLoader tag then:
|
||||
return this._getCornerstoneWADOImageLoaderTag(
|
||||
naturalizedTagOrWADOImageLoaderTag,
|
||||
instance
|
||||
);
|
||||
}
|
||||
|
||||
_getCornerstoneWADOImageLoaderTag(wadoImageLoaderTag, instance) {
|
||||
let metadata;
|
||||
|
||||
switch (wadoImageLoaderTag) {
|
||||
case WADO_IMAGE_LOADER_TAGS.GENERAL_SERIES_MODULE:
|
||||
const { SeriesDate, SeriesTime } = instance;
|
||||
|
||||
let seriesDate;
|
||||
let seriesTime;
|
||||
|
||||
if (SeriesDate) {
|
||||
seriesDate = dicomParser.parseDA(SeriesDate);
|
||||
}
|
||||
|
||||
if (SeriesTime) {
|
||||
seriesTime = dicomParser.parseTM(SeriesTime);
|
||||
}
|
||||
|
||||
metadata = {
|
||||
modality: instance.Modality,
|
||||
seriesInstanceUID: instance.SeriesInstanceUID,
|
||||
seriesNumber: instance.SeriesNumber,
|
||||
studyInstanceUID: instance.StudyInstanceUID,
|
||||
seriesDate,
|
||||
seriesTime,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.PATIENT_STUDY_MODULE:
|
||||
metadata = {
|
||||
patientAge: instance.PatientAge,
|
||||
patientSize: instance.PatientSize,
|
||||
patientWeight: instance.PatientWeight,
|
||||
};
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE:
|
||||
const { ImageOrientationPatient } = instance;
|
||||
|
||||
// Fallback for DX images.
|
||||
const PixelSpacing = getFallbackTagFromInstance(
|
||||
'PixelSpacing',
|
||||
instance
|
||||
);
|
||||
|
||||
let rowPixelSpacing;
|
||||
let columnPixelSpacing;
|
||||
|
||||
let rowCosines;
|
||||
let columnCosines;
|
||||
|
||||
if (PixelSpacing) {
|
||||
rowPixelSpacing = PixelSpacing[0];
|
||||
columnPixelSpacing = PixelSpacing[1];
|
||||
}
|
||||
|
||||
if (ImageOrientationPatient) {
|
||||
rowCosines = ImageOrientationPatient.slice(0, 3);
|
||||
columnCosines = ImageOrientationPatient.slice(3, 6);
|
||||
}
|
||||
|
||||
metadata = {
|
||||
frameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
rows: instance.Rows,
|
||||
columns: instance.Columns,
|
||||
imageOrientationPatient: ImageOrientationPatient,
|
||||
rowCosines,
|
||||
columnCosines,
|
||||
imagePositionPatient: instance.ImagePositionPatient,
|
||||
sliceThickness: instance.SliceThickness,
|
||||
sliceLocation: instance.SliceLocation,
|
||||
pixelSpacing: PixelSpacing,
|
||||
rowPixelSpacing,
|
||||
columnPixelSpacing,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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,
|
||||
};
|
||||
|
||||
let frameTime;
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE:
|
||||
const { WindowCenter, WindowWidth } = instance;
|
||||
|
||||
const numberOfFrames = this.getFromImage(
|
||||
image,
|
||||
'intString',
|
||||
'x00280008',
|
||||
'numberOfFrames',
|
||||
-1
|
||||
);
|
||||
const windowCenter = Array.isArray(WindowCenter)
|
||||
? WindowCenter
|
||||
: [WindowCenter];
|
||||
const windowWidth = Array.isArray(WindowWidth)
|
||||
? WindowWidth
|
||||
: [WindowWidth];
|
||||
|
||||
if (numberOfFrames > 0) {
|
||||
// set multi-frame image indicator
|
||||
imageInfo.isMultiframeImage = true;
|
||||
imageInfo.numberOfFrames = numberOfFrames;
|
||||
metadata = {
|
||||
windowCenter,
|
||||
windowWidth,
|
||||
};
|
||||
|
||||
// (0028,0009) = Frame Increment Pointer
|
||||
const frameIncrementPointer = this.getFrameIncrementPointer(image);
|
||||
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 (frameIncrementPointer === 'frameTimeVector') {
|
||||
// Frame Increment Pointer points to Frame Time Vector (0018,1065) field
|
||||
const frameTimeVector = this.getFrameTimeVector(image);
|
||||
if (RadiopharmaceuticalInformationSequence) {
|
||||
const RadiopharmaceuticalInformation = Array.isArray(
|
||||
RadiopharmaceuticalInformationSequence
|
||||
)
|
||||
? RadiopharmaceuticalInformationSequence[0]
|
||||
: RadiopharmaceuticalInformationSequence;
|
||||
|
||||
if (frameTimeVector instanceof Array && frameTimeVector.length > 0) {
|
||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
||||
imageInfo.frameTimeVector = frameTimeVector;
|
||||
frameTime =
|
||||
frameTimeVector.reduce((a, b) => a + b) / frameTimeVector.length;
|
||||
imageInfo.averageFrameRate = 1000 / frameTime;
|
||||
const {
|
||||
RadiopharmaceuticalStartTime,
|
||||
RadionuclideTotalDose,
|
||||
RadionuclideHalfLife,
|
||||
} = RadiopharmaceuticalInformation;
|
||||
|
||||
const radiopharmaceuticalInfo = {
|
||||
radiopharmaceuticalStartTime: dicomParser.parseTM(
|
||||
RadiopharmaceuticalStartTime
|
||||
),
|
||||
radionuclideTotalDose: RadionuclideTotalDose,
|
||||
radionuclideHalfLife: RadionuclideHalfLife,
|
||||
};
|
||||
metadata = {
|
||||
radiopharmaceuticalInfo,
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
frameIncrementPointer === 'frameTime' ||
|
||||
frameIncrementPointer === ''
|
||||
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.OVERLAY_PLANE_MODULE:
|
||||
metadata = [];
|
||||
|
||||
for (
|
||||
let overlayGroup = 0x00;
|
||||
overlayGroup <= 0x1e;
|
||||
overlayGroup += 0x02
|
||||
) {
|
||||
frameTime = this.getFrameTime(image);
|
||||
let groupStr = `60${overlayGroup.toString(16)}`;
|
||||
|
||||
if (frameTime > 0) {
|
||||
imageInfo.frameIncrementPointer = frameIncrementPointer;
|
||||
imageInfo.frameTime = frameTime;
|
||||
imageInfo.averageFrameRate = 1000 / frameTime;
|
||||
}
|
||||
}
|
||||
if (groupStr.length === 3) {
|
||||
groupStr = `600${overlayGroup.toString(16)}`;
|
||||
}
|
||||
|
||||
return imageInfo;
|
||||
const OverlayDataTag = `${groupStr}3000`;
|
||||
const OverlayData = instance[OverlayDataTag];
|
||||
|
||||
if (!OverlayData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bound reference to the provider function.
|
||||
*/
|
||||
getProvider() {
|
||||
let provider = this._provider;
|
||||
if (typeof this._provider !== FUNCTION) {
|
||||
provider = this.provider.bind(this);
|
||||
this._provider = provider;
|
||||
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);
|
||||
}
|
||||
|
||||
return provider;
|
||||
break;
|
||||
|
||||
case WADO_IMAGE_LOADER_TAGS.PATIENT_MODULE:
|
||||
const { PatientName } = instance;
|
||||
|
||||
let patientName;
|
||||
if (PatientName) {
|
||||
patientName = PatientName.Alphabetic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up metadata for Cornerstone Tools given a specified type and imageId
|
||||
* A type may be, e.g. 'study', or 'patient', or 'imagePlaneModule'. These types
|
||||
* are keys in the stored metadata objects.
|
||||
*
|
||||
* @param type
|
||||
* @param imageId
|
||||
* @returns {Object} Relevant metadata of the specified type
|
||||
*/
|
||||
provider(type, imageId) {
|
||||
const imageMetadata = this.metadataLookup.get(imageId);
|
||||
if (!imageMetadata) {
|
||||
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 metadata;
|
||||
}
|
||||
|
||||
_getInstanceData(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) {
|
||||
const study = this.studies.get(StudyInstanceUID);
|
||||
|
||||
if (!study) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageMetadata.hasOwnProperty(type)) {
|
||||
return imageMetadata[type];
|
||||
const series = study.series.get(SeriesInstanceUID);
|
||||
|
||||
if (!series) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = series.instances.get(SOPInstanceUID);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
_getUIDsFromImageID(imageId) {
|
||||
if (imageId.includes('wadors:')) {
|
||||
const strippedImageId = imageId.split('studies/')[1];
|
||||
const splitImageId = strippedImageId.split('/');
|
||||
|
||||
return {
|
||||
StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series'
|
||||
SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'
|
||||
SOPInstanceUID: splitImageId[4],
|
||||
};
|
||||
}
|
||||
if (imageId.includes('wado?requestType=WADO')) {
|
||||
const qs = queryString.parse(imageId);
|
||||
|
||||
return {
|
||||
StudyInstanceUID: qs.studyUID,
|
||||
SeriesInstanceUID: qs.seriesUID,
|
||||
SOPInstanceUID: qs.objectUID,
|
||||
};
|
||||
} else {
|
||||
// Maybe its a non-standard imageId
|
||||
return this.imageIdToUIDs.get(imageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MetadataProvider;
|
||||
const metadataProvider = new MetadataProvider();
|
||||
|
||||
export default metadataProvider;
|
||||
|
||||
const WADO_IMAGE_LOADER_TAGS = {
|
||||
// CornerstoneWADOImageLoader specific
|
||||
GENERAL_SERIES_MODULE: 'generalSeriesModule',
|
||||
PATIENT_STUDY_MODULE: 'patientStudyModule',
|
||||
IMAGE_PLANE_MODULE: 'imagePlaneModule',
|
||||
IMAGE_PIXEL_MODULE: 'imagePixelModule',
|
||||
VOI_LUT_MODULE: 'voiLutModule',
|
||||
MODALITY_LUT_MODULE: 'modalityLutModule',
|
||||
SOP_COMMON_MODULE: 'sopCommonModule',
|
||||
PET_ISOTOPE_MODULE: 'petIsotopeModule',
|
||||
OVERLAY_PLANE_MODULE: 'overlayPlaneModule',
|
||||
|
||||
// react-cornerstone-viewport specifc
|
||||
PATIENT_MODULE: 'patientModule',
|
||||
GENERAL_IMAGE_MODULE: 'generalImageModule',
|
||||
GENERAL_STUDY_MODULE: 'generalStudyModule',
|
||||
CINE_MODULE: 'cineModule',
|
||||
};
|
||||
|
||||
const INSTANCE = 'instance';
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { studyMetadataManager, updateMetaDataManager } from '../utils';
|
||||
import { studyMetadataManager } from '../utils';
|
||||
|
||||
import OHIFError from './OHIFError';
|
||||
import { StudyMetadata } from './metadata/StudyMetadata';
|
||||
import { StudyMetadataSource } from './StudyMetadataSource.js';
|
||||
import { StudySummary } from './metadata/StudySummary';
|
||||
import { retrieveStudyMetadata } from '../studies/retrieveStudyMetadata.js';
|
||||
|
||||
export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
@ -19,12 +18,12 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
|
||||
/**
|
||||
* Load study info (OHIF.viewer.Studies) and study metadata (OHIF.viewer.StudyMetadataList) for a given study.
|
||||
* @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object.
|
||||
* @param {StudyMetadata} study StudyMetadata object.
|
||||
*/
|
||||
loadStudy(study) {
|
||||
if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) {
|
||||
if (!(study instanceof StudyMetadata)) {
|
||||
throw new OHIFError(
|
||||
'OHIFStudyMetadataSource::loadStudy study is not an instance of StudySummary or StudyMetadata'
|
||||
'OHIFStudyMetadataSource::loadStudy study is not an instance of StudyMetadata'
|
||||
);
|
||||
}
|
||||
|
||||
@ -33,7 +32,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
|
||||
if (study instanceof StudyMetadata) {
|
||||
const alreadyLoaded = OHIF.viewer.Studies.findBy({
|
||||
studyInstanceUid: studyInstanceUID,
|
||||
StudyInstanceUID: studyInstanceUID,
|
||||
});
|
||||
|
||||
if (!alreadyLoaded) {
|
||||
@ -49,7 +48,7 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
// Create study metadata object
|
||||
const studyMetadata = new StudyMetadata(
|
||||
studyInfo,
|
||||
studyInfo.studyInstanceUid
|
||||
studyInfo.StudyInstanceUID
|
||||
);
|
||||
|
||||
// Get Study display sets
|
||||
@ -72,10 +71,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
// Set some studyInfo properties
|
||||
studyInfo.selected = true;
|
||||
studyInfo.displaySets = studyMetadata.getDisplaySets();
|
||||
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager(studyInfo);
|
||||
|
||||
studyMetadataManager.add(studyMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
import StudySummary from './StudySummary';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const STRING = 'string';
|
||||
const propertyReplacementMap = {
|
||||
modalities: 'ModalitiesInStudy',
|
||||
patientBirthdate: 'PatientBirthDate',
|
||||
};
|
||||
|
||||
/**
|
||||
* OHIF Viewers specialized version of StudySummary class
|
||||
*/
|
||||
export class OHIFStudySummary extends StudySummary {
|
||||
// @Override
|
||||
addTags(tagMap) {
|
||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||
const _tagMap = Object.create(null);
|
||||
for (let property in tagMap) {
|
||||
if (_hasOwn.call(tagMap, property)) {
|
||||
let standardProperty = OHIFStudySummary.getStandardPropertyName(
|
||||
property
|
||||
);
|
||||
if (standardProperty) {
|
||||
_tagMap[standardProperty] = tagMap[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
super.addTags(_tagMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a non-standard, OHIF specific, DICOM property name into a standard one.
|
||||
* @param {string} property A string representing a non-conforming keyword.
|
||||
* @returns {string|undefined} Returns a standard-conforming property name.
|
||||
*/
|
||||
static getStandardPropertyName(property) {
|
||||
let standardProperty;
|
||||
if (typeof property === STRING && property.charAt(0) !== '_') {
|
||||
if (property in propertyReplacementMap) {
|
||||
standardProperty = propertyReplacementMap[propertyReplacementMap];
|
||||
} else {
|
||||
standardProperty = property
|
||||
.replace(/^sop/, 'SOP')
|
||||
.replace(/Uid$/, 'UID')
|
||||
.replace(/Id$/, 'ID');
|
||||
standardProperty =
|
||||
standardProperty.charAt(0).toUpperCase() + standardProperty.substr(1);
|
||||
}
|
||||
}
|
||||
return standardProperty;
|
||||
}
|
||||
}
|
||||
@ -60,8 +60,8 @@ class BaseLoadingListener {
|
||||
}
|
||||
|
||||
_getProgressId() {
|
||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||
return 'StackProgress:' + displaySetInstanceUid;
|
||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||
return 'StackProgress:' + displaySetInstanceUID;
|
||||
}
|
||||
|
||||
_clearProgress() {
|
||||
@ -362,7 +362,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
|
||||
_logProgress() {
|
||||
const totalFramesCount = this.stack.imageIds.length;
|
||||
const displaySetInstanceUid = this.stack.displaySetInstanceUid;
|
||||
const displaySetInstanceUID = this.stack.displaySetInstanceUID;
|
||||
let progressBar = '[';
|
||||
|
||||
for (let i = 0; i < totalFramesCount; i++) {
|
||||
@ -371,7 +371,7 @@ class StackLoadingListener extends BaseLoadingListener {
|
||||
}
|
||||
|
||||
progressBar += ']';
|
||||
log.info(`${displaySetInstanceUid}: ${progressBar}`);
|
||||
log.info(`${displaySetInstanceUID}: ${progressBar}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,12 +388,12 @@ class StudyLoadingListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const displaySetInstanceUid = stack.displaySetInstanceUid;
|
||||
const displaySetInstanceUID = stack.displaySetInstanceUID;
|
||||
|
||||
if (!this.listeners[displaySetInstanceUid]) {
|
||||
if (!this.listeners[displaySetInstanceUID]) {
|
||||
const listener = this._createListener(stack, stackMetaData);
|
||||
if (listener) {
|
||||
this.listeners[displaySetInstanceUid] = listener;
|
||||
this.listeners[displaySetInstanceUID] = listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -424,12 +424,12 @@ class StudyLoadingListener {
|
||||
}
|
||||
|
||||
clear() {
|
||||
const displaySetInstanceUids = Object.keys(this.listeners);
|
||||
const length = displaySetInstanceUids.length;
|
||||
const displaySetInstanceUIDs = Object.keys(this.listeners);
|
||||
const length = displaySetInstanceUIDs.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const displaySetInstanceUid = displaySetInstanceUids[i];
|
||||
const displaySet = this.listeners[displaySetInstanceUid];
|
||||
const displaySetInstanceUID = displaySetInstanceUIDs[i];
|
||||
const displaySet = this.listeners[displaySetInstanceUID];
|
||||
|
||||
displaySet.destroy();
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export class StudyMetadataSource {
|
||||
|
||||
/**
|
||||
* Load study info and study metadata for a given study into the viewer.
|
||||
* @param {StudySummary|StudyMetadata} study of StudySummary or StudyMetadata object.
|
||||
* @param {StudyMetadata} study StudyMetadata object.
|
||||
*/
|
||||
loadStudy(study) {
|
||||
/**
|
||||
|
||||
@ -94,17 +94,23 @@ export class StudyPrefetcher {
|
||||
}
|
||||
|
||||
getStudy(image) {
|
||||
const studyMetadata = cornerstone.metaData.get('study', image.imageId);
|
||||
const StudyInstanceUID = cornerstone.metaData.get(
|
||||
'StudyInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
return OHIF.viewer.Studies.find(
|
||||
study => study.studyInstanceUid === studyMetadata.studyInstanceUid
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
getSeries(study, image) {
|
||||
const seriesMetadata = cornerstone.metaData.get('series', image.imageId);
|
||||
const SeriesInstanceUID = cornerstone.metaData.get(
|
||||
'SeriesInstanceUID',
|
||||
image.imageId
|
||||
);
|
||||
const studyMetadata = OHIF.viewerbase.getStudyMetadata(study);
|
||||
|
||||
return studyMetadata.getSeriesByUID(seriesMetadata.seriesInstanceUid);
|
||||
return studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
getInstance(series, image) {
|
||||
@ -112,13 +118,13 @@ export class StudyPrefetcher {
|
||||
'instance',
|
||||
image.imageId
|
||||
);
|
||||
return series.getInstanceByUID(instanceMetadata.sopInstanceUid);
|
||||
return series.getInstanceByUID(instanceMetadata.SOPInstanceUID);
|
||||
}
|
||||
|
||||
getActiveDisplaySet(displaySets, instance) {
|
||||
return displaySets.find(displaySet => {
|
||||
return displaySet.images.some(displaySetImage => {
|
||||
return displaySetImage.sopInstanceUid === instance.sopInstanceUid;
|
||||
return displaySetImage.SOPInstanceUID === instance.SOPInstanceUID;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
import {
|
||||
InstanceMetadata,
|
||||
SeriesMetadata,
|
||||
StudyMetadata,
|
||||
StudySummary,
|
||||
} from './metadata';
|
||||
import { InstanceMetadata, SeriesMetadata, StudyMetadata } from './metadata';
|
||||
|
||||
import CommandsManager from './CommandsManager.js';
|
||||
import { DICOMFileLoadingListener } from './StudyLoadingListener';
|
||||
import HotkeysManager from './HotkeysManager.js';
|
||||
import ImageSet from './ImageSet';
|
||||
import MetadataProvider from './MetadataProvider.js';
|
||||
import MetadataProvider from './MetadataProvider';
|
||||
import OHIFError from './OHIFError.js';
|
||||
import { OHIFStudyMetadataSource } from './OHIFStudyMetadataSource';
|
||||
import { StackLoadingListener } from './StudyLoadingListener';
|
||||
@ -31,7 +26,6 @@ export {
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
StudySummary,
|
||||
TypeSafeCollection,
|
||||
OHIFError,
|
||||
StudyMetadataSource,
|
||||
@ -50,7 +44,6 @@ const classes = {
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
StudySummary,
|
||||
TypeSafeCollection,
|
||||
OHIFError,
|
||||
StudyMetadataSource,
|
||||
|
||||
@ -8,20 +8,12 @@ import OHIFError from '../OHIFError.js';
|
||||
|
||||
const UNDEFINED = 'undefined';
|
||||
const STRING = 'string';
|
||||
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||
const SERIES_INSTANCE_UID = 'x0020000e';
|
||||
|
||||
export class InstanceMetadata extends Metadata {
|
||||
constructor(data, uid) {
|
||||
super(data, uid);
|
||||
// Initialize Private Properties
|
||||
Object.defineProperties(this, {
|
||||
_sopInstanceUID: {
|
||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: null,
|
||||
},
|
||||
_imageId: {
|
||||
configurable: true, // configurable so that it can be redefined in sub-classes...
|
||||
enumerable: false,
|
||||
@ -43,14 +35,14 @@ export class InstanceMetadata extends Metadata {
|
||||
*/
|
||||
_definePublicProperties() {
|
||||
/**
|
||||
* Property: this.sopInstanceUID
|
||||
* Property: this.SOPInstanceUID
|
||||
* Same as this.getSOPInstanceUID()
|
||||
* It's specially useful in contexts where a method call is not suitable like in search criteria. For example:
|
||||
* sopInstanceCollection.findBy({
|
||||
* sopInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* SOPInstanceUID: '1.2.3.4.5.6.77777.8888888.99999999999.0'
|
||||
* });
|
||||
*/
|
||||
Object.defineProperty(this, 'sopInstanceUID', {
|
||||
Object.defineProperty(this, 'SOPInstanceUID', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
@ -67,21 +59,21 @@ export class InstanceMetadata extends Metadata {
|
||||
* Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||
*/
|
||||
getStudyInstanceUID() {
|
||||
return this.getTagValue(STUDY_INSTANCE_UID, null);
|
||||
return this.getTagValue('StudyInstanceUID', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call.
|
||||
*/
|
||||
getSeriesInstanceUID() {
|
||||
return this.getTagValue(SERIES_INSTANCE_UID, null);
|
||||
return this.getTagValue('SeriesInstanceUID', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SOPInstanceUID of the current instance.
|
||||
*/
|
||||
getSOPInstanceUID() {
|
||||
return this._sopInstanceUID;
|
||||
return this.getTagValue('SOPInstanceUID', null);
|
||||
}
|
||||
|
||||
// @TODO: Improve this... (E.g.: blob data)
|
||||
@ -127,13 +119,6 @@ export class InstanceMetadata extends Metadata {
|
||||
return typeof value === STRING ? parseInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use getTagValue instead.
|
||||
*/
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
return this.getTagValue(tagOrProperty, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should be overriden by specialized classes in order to allow client libraries or viewers to take advantage of the Study Metadata API.
|
||||
*/
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { InstanceMetadata } from './InstanceMetadata';
|
||||
import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions.js';
|
||||
import getImageId from '../../utils/getImageId.js';
|
||||
|
||||
export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
@ -20,7 +19,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: instance.sopInstanceUid,
|
||||
value: instance.SOPInstanceUID,
|
||||
},
|
||||
_study: {
|
||||
configurable: false,
|
||||
@ -56,16 +55,16 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
return this._cache[tagOrProperty];
|
||||
}
|
||||
|
||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
||||
const instanceData = this._instance.metadata;
|
||||
|
||||
// Search property value in the whole study metadata chain...
|
||||
let rawValue;
|
||||
if (propertyName in this._instance) {
|
||||
rawValue = this._instance[propertyName];
|
||||
} else if (propertyName in this._series) {
|
||||
rawValue = this._series[propertyName];
|
||||
} else if (propertyName in this._study) {
|
||||
rawValue = this._study[propertyName];
|
||||
if (tagOrProperty in instanceData) {
|
||||
rawValue = instanceData[tagOrProperty];
|
||||
} else if (tagOrProperty in this._series) {
|
||||
rawValue = this._series[tagOrProperty];
|
||||
} else if (tagOrProperty in this._study) {
|
||||
rawValue = this._study[tagOrProperty];
|
||||
}
|
||||
|
||||
if (rawValue !== void 0) {
|
||||
@ -79,12 +78,10 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
|
||||
// Override
|
||||
tagExists(tagOrProperty) {
|
||||
const propertyName = OHIFInstanceMetadata.getPropertyName(tagOrProperty);
|
||||
|
||||
return (
|
||||
propertyName in this._instance ||
|
||||
propertyName in this._series ||
|
||||
propertyName in this._study
|
||||
tagOrProperty in this._instance.metadata ||
|
||||
tagOrProperty in this._series ||
|
||||
tagOrProperty in this._study
|
||||
);
|
||||
}
|
||||
|
||||
@ -97,28 +94,4 @@ export class OHIFInstanceMetadata extends InstanceMetadata {
|
||||
|
||||
return this._imageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static methods
|
||||
*/
|
||||
|
||||
// @TODO: The current mapping of standard DICOM property names to local property names is not optimal.
|
||||
// The inconsistency in property naming makes this function increasingly complex.
|
||||
// A possible solution to improve this would be adapt retriveMetadata names to use DICOM standard names as in dicomTagDescriptions.js
|
||||
static getPropertyName(tagOrProperty) {
|
||||
let propertyName;
|
||||
const tagInfo = DICOMTagDescriptions.find(tagOrProperty);
|
||||
|
||||
if (tagInfo !== void 0) {
|
||||
// This function tries to translate standard DICOM property names into local naming convention.
|
||||
propertyName = tagInfo.keyword
|
||||
.replace(/^SOP/, 'sop')
|
||||
.replace(/UID$/, 'Uid')
|
||||
.replace(/ID$/, 'Id');
|
||||
propertyName =
|
||||
propertyName.charAt(0).toLowerCase() + propertyName.substr(1);
|
||||
}
|
||||
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export class OHIFSeriesMetadata extends SeriesMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: series.seriesInstanceUid,
|
||||
value: series.SeriesInstanceUID,
|
||||
});
|
||||
|
||||
// populate internal list of instances...
|
||||
|
||||
@ -18,11 +18,11 @@ export class OHIFStudyMetadata extends StudyMetadata {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: study.studyInstanceUid,
|
||||
value: study.StudyInstanceUID,
|
||||
});
|
||||
|
||||
// populate internal list of series
|
||||
study.seriesList.forEach(series => {
|
||||
study.series.forEach(series => {
|
||||
this.addSeries(new OHIFSeriesMetadata(series, study));
|
||||
});
|
||||
}
|
||||
|
||||
@ -83,10 +83,10 @@ well (again, flexibility and extensibility are design goals). Most implemented
|
||||
methods rely on the implementation of an unimplemented method. For example, the
|
||||
method `getStringValue` from `InstanceMetadata` class, which has indeed been
|
||||
implemented and is meant to retrieve a metadata value as a string, internally
|
||||
calls the `getRawValue` method which _was NOT implemented_ and is meant to query
|
||||
calls the `getTagValue` method which _was NOT implemented_ and is meant to query
|
||||
the internal data structures for the requested metadata value and return it _as
|
||||
is_. Used in that way, an application would not benefit much from the already
|
||||
implemented methods. On the other hand, by simply overriding the `getRawValue`
|
||||
implemented methods. On the other hand, by simply overriding the `getTagValue`
|
||||
method on a specialized class to deal with the intrinsics of its internal data
|
||||
structures, this very application would now benefit from all already implemented
|
||||
methods.
|
||||
@ -99,12 +99,12 @@ The following code snippet tries to illustrate the idea:
|
||||
|
||||
class InstanceMetadata {
|
||||
[ ... ]
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
getTagValue(tagOrProperty, defaultValue) {
|
||||
// Please implement this method in a specialized subclass...
|
||||
}
|
||||
[ ... ]
|
||||
getStringValue(tagOrProperty, index, defaultValue) {
|
||||
let rawValue = this.getRawValue(tagOrProperty, '');
|
||||
let rawValue = this.getTagValue(tagOrProperty, '');
|
||||
// parse the returned value into a string...
|
||||
[ ... ]
|
||||
return stringValue;
|
||||
@ -117,11 +117,11 @@ class InstanceMetadata {
|
||||
class MyFancyAppInstanceMetadata extends InstanceMetadata {
|
||||
// Overriding this method will make all methods implemented in the super class
|
||||
// that rely on it to be immediately available...
|
||||
getRawValue(tagOrProperty, defaultValue) {
|
||||
let rawValue;
|
||||
getTagValue(tagOrProperty, defaultValue) {
|
||||
let tagValue;
|
||||
// retrieve raw value from internal data structures...
|
||||
[ ... ]
|
||||
return rawValue;
|
||||
return tagValue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,9 +135,9 @@ if (sopInstaceMetadata instanceof MyFancyAppInstanceMetadata) { // true
|
||||
if (sopInstaceMetadata instanceof InstanceMetadata) { // also true
|
||||
// this code will also be executed...
|
||||
}
|
||||
// The following will also work since the internal "getRawValue" call inside
|
||||
// The following will also work since the internal "getTagValue" call inside
|
||||
// "getStringValue" method will now be satisfied... (thanks to the override)
|
||||
let patientName = sopInstaceMetadata.getStringValue('PatientName', '');
|
||||
let PatientName = sopInstaceMetadata.getStringValue('PatientName', '');
|
||||
[ ... ]
|
||||
|
||||
```
|
||||
|
||||
@ -110,14 +110,14 @@ export class StudyMetadata extends Metadata {
|
||||
return;
|
||||
}
|
||||
|
||||
const sopClassUids = getSopClassUids(series);
|
||||
const sopClassUIDs = getSopClassUIDs(series);
|
||||
|
||||
if (sopClassHandlerModules && sopClassHandlerModules.length > 0) {
|
||||
const displaySet = _getDisplaySetFromSopClassModule(
|
||||
sopClassHandlerModules,
|
||||
series,
|
||||
study,
|
||||
sopClassUids
|
||||
sopClassUIDs
|
||||
);
|
||||
if (displaySet) {
|
||||
displaySet.sopClassModule = true;
|
||||
@ -135,10 +135,10 @@ export class StudyMetadata extends Metadata {
|
||||
// series into another display set.
|
||||
const stackableInstances = [];
|
||||
series.forEachInstance(instance => {
|
||||
// All imaging modalities must have a valid value for sopClassUid (x00080016) or rows (x00280010)
|
||||
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
|
||||
if (
|
||||
!isImage(instance.getRawValue('x00080016')) &&
|
||||
!instance.getRawValue('x00280010')
|
||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||
!instance.getTagValue('Rows')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -149,23 +149,23 @@ export class StudyMetadata extends Metadata {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
isClip: true,
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid for drag/drop purposes
|
||||
numImageFrames: instance.getRawValue('x00280008'), // Override the default value of instances.length
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID for drag/drop purposes
|
||||
numImageFrames: instance.getTagValue('NumberOfFrames'), // Override the default value of instances.length
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else if (isSingleImageModality(instance.modality)) {
|
||||
} else if (isSingleImageModality(instance.Modality)) {
|
||||
displaySet = makeDisplaySet(series, [instance]);
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
studyInstanceUid: study.getStudyInstanceUID(), // Include the study instance Uid
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
instanceNumber: instance.getRawValue('x00200013'), // Include the instance number
|
||||
acquisitionDatetime: instance.getRawValue('x0008002a'), // Include the acquisition datetime
|
||||
sopClassUIDs,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(), // Include the study instance UID
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
InstanceNumber: instance.getTagValue('InstanceNumber'), // Include the instance number
|
||||
AcquisitionDatetime: instance.getTagValue('AcquisitionDateTime'), // Include the acquisition datetime
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
} else {
|
||||
@ -175,9 +175,9 @@ export class StudyMetadata extends Metadata {
|
||||
|
||||
if (stackableInstances.length) {
|
||||
const displaySet = makeDisplaySet(series, stackableInstances);
|
||||
displaySet.setAttribute('studyInstanceUid', study.getStudyInstanceUID());
|
||||
displaySet.setAttribute('StudyInstanceUID', study.getStudyInstanceUID());
|
||||
displaySet.setAttributes({
|
||||
sopClassUids,
|
||||
sopClassUIDs,
|
||||
});
|
||||
displaySets.push(displaySet);
|
||||
}
|
||||
@ -416,7 +416,7 @@ export class StudyMetadata extends Metadata {
|
||||
* needs series sorted by the same criteria used for sorting display sets.
|
||||
*/
|
||||
sortSeriesByDisplaySets() {
|
||||
// Object for mapping display sets' index by seriesInstanceUid
|
||||
// Object for mapping display sets' index by SeriesInstanceUID
|
||||
const displaySetsMapping = {};
|
||||
|
||||
// Loop through each display set to create the mapping
|
||||
@ -428,8 +428,8 @@ export class StudyMetadata extends Metadata {
|
||||
}
|
||||
|
||||
// In case of multiframe studies, just get the first index occurence
|
||||
if (displaySetsMapping[displaySet.seriesInstanceUid] === void 0) {
|
||||
displaySetsMapping[displaySet.seriesInstanceUid] = index;
|
||||
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
|
||||
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
|
||||
}
|
||||
});
|
||||
|
||||
@ -581,8 +581,7 @@ export class StudyMetadata extends Metadata {
|
||||
const dwc = api.DICOMwebClient;
|
||||
|
||||
const isMultiFrame = instance => {
|
||||
// NumberOfFrames (0028,0008)
|
||||
return instance.getRawValue('x00280008') > 1;
|
||||
return instance.getTagValue('NumberOfFrames') > 1;
|
||||
};
|
||||
|
||||
const makeDisplaySet = (series, instances) => {
|
||||
@ -592,15 +591,15 @@ const makeDisplaySet = (series, instances) => {
|
||||
|
||||
// set appropriate attributes to image set...
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
|
||||
seriesDate: seriesData.seriesDate,
|
||||
seriesTime: seriesData.seriesTime,
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
seriesNumber: instance.getRawValue('x00200011'),
|
||||
seriesDescription: instance.getRawValue('x0008103e'),
|
||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||
SeriesDate: seriesData.SeriesDate,
|
||||
SeriesTime: seriesData.SeriesTime,
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
SeriesNumber: instance.getTagValue('SeriesNumber'),
|
||||
SeriesDescription: instance.getTagValue('SeriesDescription'),
|
||||
numImageFrames: instances.length,
|
||||
frameRate: instance.getRawValue('x00181063'),
|
||||
modality: instance.getRawValue('x00080060'),
|
||||
frameRate: instance.getTagValue('FrameTime'),
|
||||
Modality: instance.getTagValue('Modality'),
|
||||
isMultiFrame: isMultiFrame(instance),
|
||||
});
|
||||
|
||||
@ -610,19 +609,19 @@ const makeDisplaySet = (series, instances) => {
|
||||
imageSet.sortBy((a, b) => {
|
||||
// Sort by InstanceNumber (0020,0013)
|
||||
return (
|
||||
(parseInt(a.getRawValue('x00200013', 0)) || 0) -
|
||||
(parseInt(b.getRawValue('x00200013', 0)) || 0)
|
||||
(parseInt(a.getTagValue('InstanceNumber', 0)) || 0) -
|
||||
(parseInt(b.getTagValue('InstanceNumber', 0)) || 0)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Include the first image instance number (after sorted)
|
||||
imageSet.setAttribute(
|
||||
'instanceNumber',
|
||||
imageSet.getImage(0).getRawValue('x00200013')
|
||||
'InstanceNumber',
|
||||
imageSet.getImage(0).getTagValue('InstanceNumber')
|
||||
);
|
||||
|
||||
const isReconstructable = isDisplaySetReconstructable(series, instances);
|
||||
const isReconstructable = isDisplaySetReconstructable(instances);
|
||||
|
||||
imageSet.isReconstructable = isReconstructable.value;
|
||||
|
||||
@ -635,57 +634,57 @@ const makeDisplaySet = (series, instances) => {
|
||||
return imageSet;
|
||||
};
|
||||
|
||||
const isSingleImageModality = modality => {
|
||||
return modality === 'CR' || modality === 'MG' || modality === 'DX';
|
||||
const isSingleImageModality = Modality => {
|
||||
return Modality === 'CR' || Modality === 'MG' || Modality === 'DX';
|
||||
};
|
||||
|
||||
function getSopClassUids(series) {
|
||||
const uniqueSopClassUidsInSeries = new Set();
|
||||
function getSopClassUIDs(series) {
|
||||
const uniqueSopClassUIDsInSeries = new Set();
|
||||
series.forEachInstance(instance => {
|
||||
const instanceSopClassUid = instance.getRawValue('x00080016');
|
||||
const instanceSopClassUID = instance.getTagValue('SOPClassUID');
|
||||
|
||||
uniqueSopClassUidsInSeries.add(instanceSopClassUid);
|
||||
uniqueSopClassUIDsInSeries.add(instanceSopClassUID);
|
||||
});
|
||||
const sopClassUids = Array.from(uniqueSopClassUidsInSeries);
|
||||
const sopClassUIDs = Array.from(uniqueSopClassUIDsInSeries);
|
||||
|
||||
return sopClassUids;
|
||||
return sopClassUIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {SeriesMetadata} series
|
||||
* @param {StudyMetadata} study
|
||||
* @param {string[]} sopClassUids
|
||||
* @param {string[]} sopClassUIDs
|
||||
*/
|
||||
function _getDisplaySetFromSopClassModule(
|
||||
sopClassHandlerExtensions, // TODO: Update Usage
|
||||
series,
|
||||
study,
|
||||
sopClassUids
|
||||
sopClassUIDs
|
||||
) {
|
||||
// TODO: For now only use the plugins if all instances have the same sopClassUid
|
||||
if (sopClassUids.length !== 1) {
|
||||
// TODO: For now only use the plugins if all instances have the same SOPClassUID
|
||||
if (sopClassUIDs.length !== 1) {
|
||||
console.warn(
|
||||
'getDisplaySetFromSopClassPlugin: More than one SOPClassUid in the same series is not yet supported.'
|
||||
'getDisplaySetFromSopClassPlugin: More than one SOPClassUID in the same series is not yet supported.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sopClassUid = sopClassUids[0];
|
||||
const SOPClassUID = sopClassUIDs[0];
|
||||
const sopClassHandlerModules = sopClassHandlerExtensions.map(extension => {
|
||||
return extension.module;
|
||||
});
|
||||
|
||||
const handlersForSopClassUid = sopClassHandlerModules.filter(module => {
|
||||
return module.sopClassUids.includes(sopClassUid);
|
||||
const handlersForSopClassUID = sopClassHandlerModules.filter(module => {
|
||||
return module.sopClassUIDs.includes(SOPClassUID);
|
||||
});
|
||||
|
||||
// TODO: Sort by something, so we can determine which plugin to use
|
||||
if (!handlersForSopClassUid || !handlersForSopClassUid.length) {
|
||||
if (!handlersForSopClassUID || !handlersForSopClassUID.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugin = handlersForSopClassUid[0];
|
||||
const plugin = handlersForSopClassUID[0];
|
||||
const headers = DICOMWeb.getAuthorizationHeader();
|
||||
const dicomWebClient = new dwc({
|
||||
url: study.getData().wadoRoot,
|
||||
@ -698,15 +697,15 @@ function _getDisplaySetFromSopClassModule(
|
||||
dicomWebClient,
|
||||
headers
|
||||
);
|
||||
if (displaySet && !displaySet.modality) {
|
||||
if (displaySet && !displaySet.Modality) {
|
||||
const instance = series.getFirstInstance();
|
||||
displaySet.modality = instance.getRawValue('x00080060');
|
||||
displaySet.Modality = instance.getTagValue('Modality');
|
||||
}
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort series primarily by modality (i.e., series with references to other
|
||||
* Sort series primarily by Modality (i.e., series with references to other
|
||||
* series like SEG, KO or PR are grouped in the end of the list) and then by
|
||||
* series number:
|
||||
*
|
||||
@ -724,8 +723,8 @@ function _getDisplaySetFromSopClassModule(
|
||||
*/
|
||||
|
||||
function seriesSortingCriteria(a, b) {
|
||||
const isLowPriorityA = isLowPriorityModality(a.modality);
|
||||
const isLowPriorityB = isLowPriorityModality(b.modality);
|
||||
const isLowPriorityA = isLowPriorityModality(a.Modality);
|
||||
const isLowPriorityB = isLowPriorityModality(b.Modality);
|
||||
if (!isLowPriorityA && isLowPriorityB) {
|
||||
return -1;
|
||||
}
|
||||
@ -742,7 +741,7 @@ function seriesSortingCriteria(a, b) {
|
||||
*/
|
||||
function sortBySeriesNumber(a, b) {
|
||||
const seriesNumberAIsGreaterOrUndefined =
|
||||
a.seriesNumber > b.seriesNumber || (!a.seriesNumber && b.seriesNumber);
|
||||
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
|
||||
|
||||
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
|
||||
}
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
import { Metadata } from './Metadata';
|
||||
import { DICOMTagDescriptions } from '../../utils/DICOMTagDescriptions';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const STUDY_INSTANCE_UID = 'x0020000d';
|
||||
|
||||
/**
|
||||
* Class Definition
|
||||
*/
|
||||
|
||||
export class StudySummary extends Metadata {
|
||||
constructor(tagMap, attributeMap, uid) {
|
||||
// Call the superclass constructor passing an plain object with no prototype to be used as the main "_data" attribute.
|
||||
const _data = Object.create(null);
|
||||
super(_data, uid);
|
||||
|
||||
// Initialize internal tag map if first argument is given.
|
||||
if (tagMap !== void 0) {
|
||||
this.addTags(tagMap);
|
||||
}
|
||||
|
||||
// Initialize internal property map if second argument is given.
|
||||
if (attributeMap !== void 0) {
|
||||
this.setCustomAttributes(attributeMap);
|
||||
}
|
||||
}
|
||||
|
||||
getStudyInstanceUID() {
|
||||
// This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API
|
||||
return this.getTagValue(STUDY_INSTANCE_UID) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append tags to internal tag map.
|
||||
* @param {Object} tagMap An object whose own properties will be used as tag values and appended to internal tag map.
|
||||
*/
|
||||
addTags(tagMap) {
|
||||
const _hasOwn = Object.prototype.hasOwnProperty;
|
||||
const _data = this._data;
|
||||
for (let tag in tagMap) {
|
||||
if (_hasOwn.call(tagMap, tag)) {
|
||||
const description = DICOMTagDescriptions.find(tag);
|
||||
// When a description is available, use its tag as internal key...
|
||||
if (description) {
|
||||
_data[description.tag] = tagMap[tag];
|
||||
} else {
|
||||
_data[tag] = tagMap[tag];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagExists(tagName) {
|
||||
const _data = this._data;
|
||||
const description = DICOMTagDescriptions.find(tagName);
|
||||
if (description) {
|
||||
return description.tag in _data;
|
||||
}
|
||||
return tagName in _data;
|
||||
}
|
||||
|
||||
getTagValue(tagName) {
|
||||
const _data = this._data;
|
||||
const description = DICOMTagDescriptions.find(tagName);
|
||||
if (description) {
|
||||
return _data[description.tag];
|
||||
}
|
||||
return _data[tagName];
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
export class WadoRsMetaDataBuilder {
|
||||
constructor() {
|
||||
this.tags = {};
|
||||
}
|
||||
|
||||
addTag(tag, value, multi) {
|
||||
this.tags[tag] = {
|
||||
tag,
|
||||
value,
|
||||
multi,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const json = {};
|
||||
const keys = Object.keys(this.tags);
|
||||
|
||||
keys.forEach(key => {
|
||||
if (!this.tags.hasOwnProperty(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = this.tags[key];
|
||||
const multi = !!tag.multi;
|
||||
let value = tag.value;
|
||||
|
||||
if (value == null || (value.length === 1 && value[0] == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && multi) {
|
||||
value = value.split('\\');
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
json[key] = {
|
||||
Value: value,
|
||||
};
|
||||
});
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,9 @@ import { OHIFSeriesMetadata } from './OHIFSeriesMetadata';
|
||||
import { OHIFStudyMetadata } from './OHIFStudyMetadata';
|
||||
import { SeriesMetadata } from './SeriesMetadata';
|
||||
import { StudyMetadata } from './StudyMetadata';
|
||||
import { StudySummary } from './StudySummary';
|
||||
import { WadoRsMetaDataBuilder } from './WadoRsMetaDataBuilder';
|
||||
|
||||
const metadata = {
|
||||
Metadata,
|
||||
StudySummary,
|
||||
WadoRsMetaDataBuilder,
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
@ -22,8 +18,6 @@ const metadata = {
|
||||
|
||||
export {
|
||||
Metadata,
|
||||
StudySummary,
|
||||
WadoRsMetaDataBuilder,
|
||||
StudyMetadata,
|
||||
SeriesMetadata,
|
||||
InstanceMetadata,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import MetadataProvider from './classes/MetadataProvider';
|
||||
import metadataProvider from './classes/MetadataProvider';
|
||||
import {
|
||||
getBoundingBox,
|
||||
pixelToPage,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from './lib/cornerstone.js';
|
||||
|
||||
const cornerstone = {
|
||||
MetadataProvider,
|
||||
metadataProvider,
|
||||
getBoundingBox,
|
||||
pixelToPage,
|
||||
repositionTextBox,
|
||||
|
||||
@ -6,24 +6,19 @@ import { CustomAttributeRetrievalCallbacks } from './customAttributes';
|
||||
/**
|
||||
* Import Constants
|
||||
*/
|
||||
const { StudySummary, InstanceMetadata } = metadata;
|
||||
const { InstanceMetadata } = metadata;
|
||||
|
||||
/**
|
||||
* Match a Metadata instance against rules using Validate.js for validation.
|
||||
* @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object
|
||||
* @param {InstanceMetadata} metadataInstance Metadata instance object
|
||||
* @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match
|
||||
* @return {Object} Matching Object with score and details (which rule passed or failed)
|
||||
*/
|
||||
const match = (metadataInstance, rules) => {
|
||||
// Make sure the supplied data is valid.
|
||||
if (
|
||||
!(
|
||||
metadataInstance instanceof StudySummary ||
|
||||
metadataInstance instanceof InstanceMetadata
|
||||
)
|
||||
) {
|
||||
if (!(metadataInstance instanceof InstanceMetadata)) {
|
||||
throw new OHIFError(
|
||||
'HPMatcher::match metadataInstance must be an instance of StudySummary or InstanceMetadata'
|
||||
'HPMatcher::match metadataInstance must be an instance of InstanceMetadata'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import { ProtocolStore } from './protocolStore/classes';
|
||||
/**
|
||||
* Import Constants
|
||||
*/
|
||||
const { StudyMetadata, InstanceMetadata, StudySummary } = metadata;
|
||||
const { StudyMetadata, InstanceMetadata } = metadata;
|
||||
|
||||
// Useful constants
|
||||
const ABSTRACT_PRIOR_VALUE = 'abstractPriorValue';
|
||||
@ -298,10 +298,7 @@ export default class ProtocolEngine {
|
||||
}
|
||||
|
||||
// Invalid data
|
||||
if (
|
||||
!(priorStudy instanceof StudyMetadata) &&
|
||||
!(priorStudy instanceof StudySummary)
|
||||
) {
|
||||
if (!priorStudy instanceof StudyMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -385,11 +382,9 @@ export default class ProtocolEngine {
|
||||
// This tests to make sure there is actually image data in this instance
|
||||
// TODO: Change this when we add PDF and MPEG support
|
||||
// See https://ohiforg.atlassian.net/browse/LT-227
|
||||
// sopClassUid = x00080016
|
||||
// rows = x00280010
|
||||
if (
|
||||
!isImage(instance.getTagValue('x00080016')) &&
|
||||
!instance.getTagValue('x00280010')
|
||||
!isImage(instance.getTagValue('SOPClassUID')) &&
|
||||
!instance.getTagValue('Rows')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -436,19 +431,19 @@ export default class ProtocolEngine {
|
||||
const currentSOPInstanceUID = instance.getSOPInstanceUID();
|
||||
|
||||
const imageDetails = {
|
||||
studyInstanceUid: study.getStudyInstanceUID(),
|
||||
seriesInstanceUid: series.getSeriesInstanceUID(),
|
||||
sopInstanceUid: currentSOPInstanceUID,
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
SOPInstanceUID: currentSOPInstanceUID,
|
||||
currentImageIdIndex: index,
|
||||
matchingScore: totalMatchScore,
|
||||
matchDetails: matchDetails,
|
||||
sortingInfo: {
|
||||
score: totalMatchScore,
|
||||
study:
|
||||
instance.getTagValue('x00080020') +
|
||||
instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030
|
||||
series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011
|
||||
instance: parseInt(instance.getTagValue('x00200013')), // TODO: change for acquisitionTime InstanceNumber = x00200013
|
||||
instance.getTagValue('StudyDate') +
|
||||
instance.getTagValue('StudyTime'),
|
||||
series: parseInt(instance.getTagValue('SeriesNumber')), // TODO: change for seriesDateTime
|
||||
instance: parseInt(instance.getTagValue('InstanceNumber')), // TODO: change for acquisitionTime
|
||||
},
|
||||
};
|
||||
|
||||
@ -461,7 +456,7 @@ export default class ProtocolEngine {
|
||||
|
||||
// If the instance was found, set the displaySet ID
|
||||
if (displaySet) {
|
||||
imageDetails.displaySetInstanceUid = displaySet.getUID();
|
||||
imageDetails.displaySetInstanceUID = displaySet.getUID();
|
||||
imageDetails.imageId = instance.getImageId();
|
||||
}
|
||||
|
||||
@ -654,25 +649,25 @@ export default class ProtocolEngine {
|
||||
}
|
||||
|
||||
if (currentMatch && currentMatch.imageId) {
|
||||
currentViewportData.studyInstanceUid = currentMatch.studyInstanceUid;
|
||||
currentViewportData.seriesInstanceUid = currentMatch.seriesInstanceUid;
|
||||
currentViewportData.sopInstanceUid = currentMatch.sopInstanceUid;
|
||||
currentViewportData.StudyInstanceUID = currentMatch.StudyInstanceUID;
|
||||
currentViewportData.SeriesInstanceUID = currentMatch.SeriesInstanceUID;
|
||||
currentViewportData.SOPInstanceUID = currentMatch.SOPInstanceUID;
|
||||
currentViewportData.currentImageIdIndex =
|
||||
currentMatch.currentImageIdIndex;
|
||||
currentViewportData.displaySetInstanceUid =
|
||||
currentMatch.displaySetInstanceUid;
|
||||
currentViewportData.displaySetInstanceUID =
|
||||
currentMatch.displaySetInstanceUID;
|
||||
currentViewportData.imageId = currentMatch.imageId;
|
||||
}
|
||||
|
||||
// @TODO Why should we throw an exception when a best match is not found? This was aborting the whole process.
|
||||
// if (!currentViewportData.displaySetInstanceUid) {
|
||||
// if (!currentViewportData.displaySetInstanceUID) {
|
||||
// throw new OHIFError('ProtocolEngine::updateViewports No matching display set found?');
|
||||
// }
|
||||
|
||||
viewportData.push(currentViewportData);
|
||||
});
|
||||
|
||||
this.setLayout(layoutProps.rows, layoutProps.columns);
|
||||
this.setLayout(layoutProps.Rows, layoutProps.Columns);
|
||||
|
||||
if (typeof this.options.setViewportSpecificData !== 'function') {
|
||||
log.error(
|
||||
|
||||
@ -6,7 +6,7 @@ const EQUALS_REGEXP = /^equals$/;
|
||||
/**
|
||||
* This Class represents a Rule to be evaluated given a set of attributes
|
||||
* Rules have:
|
||||
* - An attribute (e.g. 'seriesDescription')
|
||||
* - An attribute (e.g. 'SeriesDescription')
|
||||
* - A constraint Object, in the form required by Validate.js:
|
||||
*
|
||||
* rule.constraint = {
|
||||
|
||||
@ -45,9 +45,9 @@ export default class ViewportStructure {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
// For the typical grid layout, we only need to multiply rows by columns to
|
||||
// For the typical grid layout, we only need to multiply Rows by Columns to
|
||||
// obtain the number of viewports
|
||||
return this.properties.rows * this.properties.columns;
|
||||
return this.properties.Rows * this.properties.Columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ function getDefaultProtocol() {
|
||||
protocol.locked = true;
|
||||
|
||||
const oneByOne = new ViewportStructure('grid', {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
Rows: 1,
|
||||
Columns: 1,
|
||||
});
|
||||
|
||||
const viewport = new Viewport();
|
||||
|
||||
@ -14,8 +14,8 @@ function getMRTwoByTwoTest() {
|
||||
proto.locked = true;
|
||||
// Use http://localhost:3000/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78
|
||||
|
||||
var studyInstanceUid = new ProtocolMatchingRule(
|
||||
'studyInstanceUid',
|
||||
var StudyInstanceUID = new ProtocolMatchingRule(
|
||||
'StudyInstanceUID',
|
||||
{
|
||||
equals: {
|
||||
value: '1.2.840.113619.2.5.1762583153.215519.978957063.78',
|
||||
@ -24,30 +24,30 @@ function getMRTwoByTwoTest() {
|
||||
true
|
||||
);
|
||||
|
||||
proto.addProtocolMatchingRule(studyInstanceUid);
|
||||
proto.addProtocolMatchingRule(StudyInstanceUID);
|
||||
|
||||
var oneByTwo = new ViewportStructure('grid', {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
});
|
||||
|
||||
// Stage 1
|
||||
var left = new Viewport();
|
||||
var right = new Viewport();
|
||||
|
||||
var firstSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var firstSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 1,
|
||||
},
|
||||
});
|
||||
|
||||
var secondSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var secondSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 2,
|
||||
},
|
||||
});
|
||||
|
||||
var thirdImage = new ImageMatchingRule('instanceNumber', {
|
||||
var thirdImage = new ImageMatchingRule('InstanceNumber', {
|
||||
equals: {
|
||||
value: 3,
|
||||
},
|
||||
@ -67,19 +67,19 @@ function getMRTwoByTwoTest() {
|
||||
|
||||
// Stage 2
|
||||
var twoByOne = new ViewportStructure('grid', {
|
||||
rows: 2,
|
||||
columns: 1,
|
||||
Rows: 2,
|
||||
Columns: 1,
|
||||
});
|
||||
var left2 = new Viewport();
|
||||
var right2 = new Viewport();
|
||||
|
||||
var fourthSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var fourthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 4,
|
||||
},
|
||||
});
|
||||
|
||||
var fifthSeries = new SeriesMatchingRule('seriesNumber', {
|
||||
var fifthSeries = new SeriesMatchingRule('SeriesNumber', {
|
||||
equals: {
|
||||
value: 5,
|
||||
},
|
||||
@ -133,8 +133,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -195,8 +195,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -257,8 +257,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -402,8 +402,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -464,8 +464,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -548,8 +548,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -737,8 +737,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
Rows: 1,
|
||||
Columns: 1,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -770,8 +770,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -931,8 +931,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -993,8 +993,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -1044,8 +1044,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -1127,8 +1127,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -1190,8 +1190,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 1,
|
||||
columns: 2,
|
||||
Rows: 1,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -1275,8 +1275,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
@ -1386,8 +1386,8 @@ function getDemoProtocols() {
|
||||
viewportStructure: {
|
||||
type: 'grid',
|
||||
properties: {
|
||||
rows: 2,
|
||||
columns: 2,
|
||||
Rows: 2,
|
||||
Columns: 2,
|
||||
},
|
||||
layoutTemplateName: 'gridLayout',
|
||||
},
|
||||
|
||||
@ -21,7 +21,7 @@ function getBoundingBox(context, textLines, x, y, options) {
|
||||
// Get the text width in the current font
|
||||
const width = context.measureText(text).width;
|
||||
|
||||
// Find the maximum with for all the text rows;
|
||||
// Find the maximum with for all the text Rows;
|
||||
maxWidth = Math.max(maxWidth, width);
|
||||
});
|
||||
|
||||
|
||||
@ -8,9 +8,8 @@ import guid from '../../utils/guid';
|
||||
import studyMetadataManager from '../../utils/studyMetadataManager';
|
||||
import { measurementApiDefaultConfig } from './../configuration.js';
|
||||
|
||||
|
||||
const configuration = {
|
||||
...measurementApiDefaultConfig
|
||||
...measurementApiDefaultConfig,
|
||||
};
|
||||
|
||||
export default class MeasurementApi {
|
||||
@ -99,8 +98,8 @@ export default class MeasurementApi {
|
||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
// Stop here if the metadata for the measurement's study is not loaded yet
|
||||
const { studyInstanceUid } = measurement;
|
||||
const metadata = studyMetadataManager.get(studyInstanceUid);
|
||||
const { StudyInstanceUID } = measurement;
|
||||
const metadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
if (!metadata) return;
|
||||
|
||||
// Iterate each child tool if the current tool has children
|
||||
@ -221,7 +220,7 @@ export default class MeasurementApi {
|
||||
this.options.onMeasurementsUpdated(Object.assign({}, this.tools));
|
||||
}
|
||||
|
||||
retrieveMeasurements(patientId, timepointIds) {
|
||||
retrieveMeasurements(PatientID, timepointIds) {
|
||||
const retrievalFn = configuration.dataExchange.retrieve;
|
||||
const { server } = configuration;
|
||||
if (typeof retrievalFn !== 'function') {
|
||||
@ -297,9 +296,9 @@ export default class MeasurementApi {
|
||||
: null;
|
||||
const timepoints = this.timepointApi.all(timepointFilter);
|
||||
const timepointIds = timepoints.map(t => t.timepointId);
|
||||
const patientId = timepoints[0].patientId;
|
||||
const PatientID = timepoints[0].PatientID;
|
||||
const filter = {
|
||||
patientId,
|
||||
PatientID,
|
||||
timepointIds,
|
||||
};
|
||||
|
||||
@ -576,17 +575,17 @@ export default class MeasurementApi {
|
||||
|
||||
const toolGroupId = this.toolsGroupsMap[measurementData.toolType];
|
||||
|
||||
// TODO: Remove TrialPatientLocationUid from here and override it somehow
|
||||
// TODO: Remove TrialPatientLocationUID from here and override it somehow
|
||||
// by dependant applications. Here we should use the location attribute instead of the uid
|
||||
let filter;
|
||||
const uid =
|
||||
measurementData.additionalData &&
|
||||
measurementData.additionalData.TrialPatientLocationUid;
|
||||
measurementData.additionalData.TrialPatientLocationUID;
|
||||
if (uid) {
|
||||
filter = tool =>
|
||||
tool._id !== measurementData._id &&
|
||||
tool.additionalData &&
|
||||
tool.additionalData.TrialPatientLocationUid === uid;
|
||||
tool.additionalData.TrialPatientLocationUID === uid;
|
||||
} else {
|
||||
filter = tool =>
|
||||
tool._id !== measurementData._id &&
|
||||
@ -691,8 +690,8 @@ export default class MeasurementApi {
|
||||
|
||||
// Get the timepoint
|
||||
let timepoint;
|
||||
if (measurement.studyInstanceUid) {
|
||||
timepoint = this.timepointApi.study(measurement.studyInstanceUid)[0];
|
||||
if (measurement.StudyInstanceUID) {
|
||||
timepoint = this.timepointApi.study(measurement.StudyInstanceUID)[0];
|
||||
} else {
|
||||
const { timepointId } = measurement;
|
||||
timepoint = this.timepointApi.timepoints.find(
|
||||
@ -760,7 +759,7 @@ export default class MeasurementApi {
|
||||
measurement.lesionNamingNumber = found.lesionNamingNumber;
|
||||
measurement.measurementNumber = found.measurementNumber;
|
||||
|
||||
// TODO: Remove TrialPatientLocationUid from here and override it somehow
|
||||
// TODO: Remove TrialPatientLocationUID from here and override it somehow
|
||||
// by dependant applications
|
||||
|
||||
// Change the update object to set the same number, additionalData,
|
||||
@ -768,8 +767,8 @@ export default class MeasurementApi {
|
||||
updateObject.lesionNamingNumber = found.lesionNamingNumber;
|
||||
updateObject.measurementNumber = found.measurementNumber;
|
||||
updateObject.additionalData = measurement.additionalData || {};
|
||||
updateObject.additionalData.TrialPatientLocationUid =
|
||||
found.additionalData && found.additionalData.TrialPatientLocationUid;
|
||||
updateObject.additionalData.TrialPatientLocationUID =
|
||||
found.additionalData && found.additionalData.TrialPatientLocationUID;
|
||||
updateObject.location = found.location;
|
||||
updateObject.label = found.label;
|
||||
updateObject.description = found.description;
|
||||
@ -807,7 +806,7 @@ export default class MeasurementApi {
|
||||
toolId: toolType,
|
||||
toolItemId: addedMeasurement._id,
|
||||
timepointId: timepoint.timepointId,
|
||||
studyInstanceUid: addedMeasurement.studyInstanceUid,
|
||||
StudyInstanceUID: addedMeasurement.StudyInstanceUID,
|
||||
createdAt: addedMeasurement.createdAt,
|
||||
lesionNamingNumber: addedMeasurement.lesionNamingNumber,
|
||||
measurementNumber: addedMeasurement.measurementNumber,
|
||||
|
||||
@ -55,7 +55,7 @@ export default class TimepointApi {
|
||||
});
|
||||
const filteredTimepoints = sortedTimepoints.find(
|
||||
tp =>
|
||||
tp.patientId === timepoint.patientId &&
|
||||
tp.PatientID === timepoint.PatientID &&
|
||||
tp.timepointType === timepoint.timepointType
|
||||
);
|
||||
|
||||
@ -129,14 +129,14 @@ export default class TimepointApi {
|
||||
);
|
||||
}
|
||||
|
||||
disassociateStudy(timepointIds, studyInstanceUid) {
|
||||
disassociateStudy(timepointIds, StudyInstanceUID) {
|
||||
const disassociateFn = configuration.dataExchange.disassociate;
|
||||
if (typeof disassociateFn !== 'function') {
|
||||
log.error('Study disassociate function has not been configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
disassociateFn(timepointIds, studyInstanceUid).then(() => {
|
||||
disassociateFn(timepointIds, StudyInstanceUID).then(() => {
|
||||
log.info('Disassociation completed');
|
||||
|
||||
this.timepoints = [];
|
||||
@ -510,9 +510,9 @@ export default class TimepointApi {
|
||||
}
|
||||
|
||||
// Return only the timepoints for the given study
|
||||
study(studyInstanceUid) {
|
||||
study(StudyInstanceUID) {
|
||||
return this.all().filter(timepoint =>
|
||||
timepoint.studyInstanceUids.includes(studyInstanceUid)
|
||||
timepoint.studyInstanceUIDs.includes(StudyInstanceUID)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -21,12 +21,12 @@ class ConformanceCriteria {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
loadStudy(studyInstanceUid) {
|
||||
loadStudy(StudyInstanceUID) {
|
||||
if (typeof this.options.loadStudy !== 'function') {
|
||||
throw new Error('loadStudy callback is not defined');
|
||||
}
|
||||
|
||||
return this.options.loadStudy(null, studyInstanceUid);
|
||||
return this.options.loadStudy(null, StudyInstanceUID);
|
||||
}
|
||||
|
||||
async validate(trialCriteriaType) {
|
||||
@ -181,7 +181,7 @@ class ConformanceCriteria {
|
||||
const measurements = this.measurementApi.fetch(measurementType);
|
||||
|
||||
measurements.forEach(measurement => {
|
||||
const { studyInstanceUid } = measurement;
|
||||
const { StudyInstanceUID } = measurement;
|
||||
|
||||
const timepointId = measurement.timepointId;
|
||||
const timepoint =
|
||||
@ -195,7 +195,7 @@ class ConformanceCriteria {
|
||||
return;
|
||||
}
|
||||
|
||||
const promise = this.loadStudy(studyInstanceUid);
|
||||
const promise = this.loadStudy(StudyInstanceUID);
|
||||
promise.then(
|
||||
studyMetadata => {
|
||||
data[measurementType].push({
|
||||
|
||||
@ -65,7 +65,7 @@ export const MeasurementsLengthSchema = {
|
||||
},
|
||||
isNodal: {
|
||||
label: 'Filter to evaluate only nodal or extranodal measurements',
|
||||
type: 'boolean'
|
||||
type: 'boolean',
|
||||
},
|
||||
message: {
|
||||
label: 'Message to be displayed in case of nonconformity',
|
||||
@ -119,19 +119,24 @@ export class MeasurementsLengthCriterion extends BaseCriterion {
|
||||
shortestDiameter = (child && child.shortestDiameter) || 0;
|
||||
}
|
||||
|
||||
const { sliceThickness } = metadata;
|
||||
const modality = (metadata.getRawValue('x00080060') || '').toUpperCase();
|
||||
const { SliceThickness } = metadata;
|
||||
|
||||
// Stop here if the measurement does not match the modality and location filters
|
||||
if (typeof isNodal === 'boolean' && typeof options.isNodal === 'boolean' && options.isNodal !== isNodal)
|
||||
const Modality = metadata.getTagValue('Modality') || '';
|
||||
|
||||
// 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;
|
||||
if (options.locationIn && options.locationIn.indexOf(location) === -1)
|
||||
return;
|
||||
if (options.modalityIn && options.modalityIn.indexOf(modality) === -1)
|
||||
if (options.modalityIn && options.modalityIn.indexOf(Modality) === -1)
|
||||
return;
|
||||
if (options.locationNotIn && options.locationNotIn.indexOf(location) > -1)
|
||||
return;
|
||||
if (options.modalityNotIn && options.modalityNotIn.indexOf(modality) > -1)
|
||||
if (options.modalityNotIn && options.modalityNotIn.indexOf(Modality) > -1)
|
||||
return;
|
||||
|
||||
// Check the measurement length
|
||||
@ -139,11 +144,11 @@ export class MeasurementsLengthCriterion extends BaseCriterion {
|
||||
(options.longAxis && longestDiameter < options.longAxis) ||
|
||||
(options.shortAxis && shortestDiameter < options.shortAxis) ||
|
||||
(longMultiplier &&
|
||||
!isNaN(sliceThickness) &&
|
||||
longestDiameter < longMultiplier * sliceThickness) ||
|
||||
!isNaN(SliceThickness) &&
|
||||
longestDiameter < longMultiplier * SliceThickness) ||
|
||||
(shortMultiplier &&
|
||||
!isNaN(sliceThickness) &&
|
||||
shortestDiameter < shortMultiplier * sliceThickness);
|
||||
!isNaN(SliceThickness) &&
|
||||
shortestDiameter < shortMultiplier * SliceThickness);
|
||||
|
||||
// Mark this measurement as invalid if some of the checks have failed
|
||||
if (failed) {
|
||||
|
||||
@ -32,7 +32,7 @@ export const ModalitySchema = {
|
||||
|
||||
/*
|
||||
* ModalityCriteria
|
||||
* Check if a modality is allowed or denied
|
||||
* Check if a Modality is allowed or denied
|
||||
* Options:
|
||||
* method (string): Specify if it\'s goinig to "allow" or "deny" the modalities
|
||||
* measurementTypes (string[]): List of measurement types that will be evaluated
|
||||
@ -56,16 +56,14 @@ export class ModalityCriterion extends BaseCriterion {
|
||||
|
||||
items.forEach(item => {
|
||||
const { measurement, metadata } = item;
|
||||
const modality = (
|
||||
metadata.getRawValue('x00080060') || ''
|
||||
).toUpperCase();
|
||||
const Modality = metadata.getTagValue('Modality') || '';
|
||||
|
||||
if (
|
||||
(validationMethod === 'allow' && !modalitiesSet.has(modality)) ||
|
||||
(validationMethod === 'deny' && modalitiesSet.has(modality))
|
||||
(validationMethod === 'allow' && !modalitiesSet.has(Modality)) ||
|
||||
(validationMethod === 'deny' && modalitiesSet.has(Modality))
|
||||
) {
|
||||
measurements.push(measurement);
|
||||
invalidModalities.add(modality);
|
||||
invalidModalities.add(Modality);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -74,7 +72,7 @@ export class ModalityCriterion extends BaseCriterion {
|
||||
const uniqueModalities = Array.from(invalidModalities);
|
||||
const uniqueModalitiesText = uniqueModalities.join(', ');
|
||||
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`;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import log from '../log';
|
||||
|
||||
export const retrieveMeasurements = (patientId, timepointIds) => {
|
||||
export const retrieveMeasurements = (PatientID, timepointIds) => {
|
||||
log.error('retrieveMeasurements');
|
||||
return Promise.resolve();
|
||||
};
|
||||
@ -30,7 +30,7 @@ export const removeTimepoint = timepointId => {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export const disassociateStudy = (timepointIds, studyInstanceUid) => {
|
||||
export const disassociateStudy = (timepointIds, StudyInstanceUID) => {
|
||||
log.error('disassociateStudy');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
@ -5,32 +5,30 @@ export default function(element) {
|
||||
const enabledElement = cornerstone.getEnabledElement(element);
|
||||
const imageId = enabledElement.image.imageId;
|
||||
|
||||
// Get studyInstanceUid & patientId
|
||||
const study = cornerstone.metaData.get('study', imageId);
|
||||
const studyInstanceUid = study.studyInstanceUid;
|
||||
const patientId = study.patientId;
|
||||
// Get StudyInstanceUID & PatientID
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
PatientID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = cornerstone.metaData.get('instance', imageId);
|
||||
|
||||
// Get seriesInstanceUid
|
||||
const series = cornerstone.metaData.get('series', imageId);
|
||||
const seriesInstanceUid = series.seriesInstanceUid;
|
||||
|
||||
// Get sopInstanceUid
|
||||
const sopInstance = cornerstone.metaData.get('instance', imageId);
|
||||
const sopInstanceUid = sopInstance.sopInstanceUid;
|
||||
const frameIndex = sopInstance.frame || 0;
|
||||
const splitImageId = imageId.split('&frame');
|
||||
const frameIndex =
|
||||
splitImageId[1] !== undefined ? Number(splitImageId[1]) : 0;
|
||||
|
||||
const imagePath = [
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
].join('_');
|
||||
|
||||
return {
|
||||
patientId,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
PatientID,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
imagePath,
|
||||
};
|
||||
|
||||
@ -2,13 +2,13 @@ import studyMetadataManager from '../../utils/studyMetadataManager';
|
||||
|
||||
export default function(imagePath, thumbnail = false) {
|
||||
const [
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
] = imagePath.split('_');
|
||||
const studyMetadata = studyMetadataManager.get(studyInstanceUid);
|
||||
const series = studyMetadata.getSeriesByUID(seriesInstanceUid);
|
||||
const instance = series.getInstanceByUID(sopInstanceUid);
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
const series = studyMetadata.getSeriesByUID(SeriesInstanceUID);
|
||||
const instance = series.getInstanceByUID(SOPInstanceUID);
|
||||
return instance.getImageId(frameIndex, thumbnail);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
const parentMeasurement = collection.find(
|
||||
t =>
|
||||
t.toolType === tool.parentTool &&
|
||||
t.patientId === imageAttributes.patientId &&
|
||||
t.PatientID === imageAttributes.PatientID &&
|
||||
t[tool.attribute] === null
|
||||
);
|
||||
|
||||
@ -63,8 +63,8 @@ export default function({ eventData, tool, toolGroupId, toolGroup }) {
|
||||
toolType: tool.parentTool,
|
||||
lesionNamingNumber: measurementData.lesionNamingNumber,
|
||||
userId: user.getUserId(),
|
||||
patientId: imageAttributes.patientId,
|
||||
studyInstanceUid: imageAttributes.studyInstanceUid,
|
||||
PatientID: imageAttributes.PatientID,
|
||||
StudyInstanceUID: imageAttributes.StudyInstanceUID,
|
||||
};
|
||||
|
||||
measurement[tool.attribute] = Object.assign(
|
||||
|
||||
@ -110,9 +110,9 @@ export const setMeasurements = state => ({
|
||||
state,
|
||||
});
|
||||
|
||||
export const setStudyData = (studyInstanceUid, data) => ({
|
||||
export const setStudyData = (StudyInstanceUID, data) => ({
|
||||
type: 'SET_STUDY_DATA',
|
||||
studyInstanceUid,
|
||||
StudyInstanceUID,
|
||||
data,
|
||||
});
|
||||
|
||||
|
||||
@ -29,20 +29,20 @@ describe('actions', () => {
|
||||
describe('viewport action creators', () => {
|
||||
it('should create an action to set the viewport specific data', () => {
|
||||
const viewportSpecificData = {
|
||||
displaySetInstanceUid: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
||||
seriesDate: '20151026',
|
||||
seriesTime: '082611.370000',
|
||||
seriesInstanceUid:
|
||||
displaySetInstanceUID: 'ef859a23-4631-93ab-d26b-7940a822c699',
|
||||
SeriesDate: '20151026',
|
||||
SeriesTime: '082611.370000',
|
||||
SeriesInstanceUID:
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170126085406.5',
|
||||
seriesNumber: 2,
|
||||
seriesDescription: 'Chest 3x3 Soft',
|
||||
SeriesNumber: 2,
|
||||
SeriesDescription: 'Chest 3x3 Soft',
|
||||
numImageFrames: 126,
|
||||
modality: 'CT',
|
||||
Modality: 'CT',
|
||||
isMultiFrame: false,
|
||||
instanceNumber: 1,
|
||||
studyInstanceUid:
|
||||
InstanceNumber: 1,
|
||||
StudyInstanceUID:
|
||||
'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',
|
||||
viewport: {
|
||||
zoomScale: null,
|
||||
|
||||
@ -8,7 +8,7 @@ const servers = (state = defaultState, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_STUDY_DATA':
|
||||
const updatedStudyData = cloneDeep(state).studyData;
|
||||
updatedStudyData[action.studyInstanceUid] = action.data;
|
||||
updatedStudyData[action.StudyInstanceUID] = action.data;
|
||||
|
||||
return Object.assign({}, state, { studyData: updatedStudyData });
|
||||
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.
|
||||
*
|
||||
* @param {Number} numRows
|
||||
@ -49,7 +49,7 @@ const findActiveViewportSpecificData = (
|
||||
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} numColumns
|
||||
|
||||
@ -15,8 +15,8 @@ import guid from '../../utils/guid';
|
||||
*
|
||||
* @typedef {Object} Measurement
|
||||
* @property {number} id -
|
||||
* @property {string} sopInstanceUID -
|
||||
* @property {string} frameOfReferenceUID -
|
||||
* @property {string} sopInstanceUid -
|
||||
* @property {string} FrameOfReferenceUID -
|
||||
* @property {string} referenceSeriesUID -
|
||||
* @property {string} label -
|
||||
* @property {string} description -
|
||||
@ -30,8 +30,8 @@ import guid from '../../utils/guid';
|
||||
/* Measurement schema keys for object validation. */
|
||||
const MEASUREMENT_SCHEMA_KEYS = [
|
||||
'id',
|
||||
'sopInstanceUID',
|
||||
'frameOfReferenceUID',
|
||||
'SOPInstanceUID',
|
||||
'FrameOfReferenceUID',
|
||||
'referenceSeriesUID',
|
||||
'label',
|
||||
'description',
|
||||
@ -197,7 +197,11 @@ class MeasurementService {
|
||||
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;
|
||||
}
|
||||
|
||||
const mapping = this._getMappingByMeasurementSource(measurementId, definition);
|
||||
const mapping = this._getMappingByMeasurementSource(
|
||||
measurementId,
|
||||
definition
|
||||
);
|
||||
if (mapping) return mapping.toSourceSchema(measurement, definition);
|
||||
|
||||
const measurement = this.getMeasurement(measurementId);
|
||||
const matchingMapping = this._getMatchingMapping(source, definition, measurement);
|
||||
const matchingMapping = this._getMatchingMapping(
|
||||
source,
|
||||
definition,
|
||||
measurement
|
||||
);
|
||||
|
||||
if (matchingMapping) {
|
||||
log.info('Matching mapping found:', matchingMapping);
|
||||
@ -249,12 +260,15 @@ class MeasurementService {
|
||||
const sourceInfo = this._getSourceInfo(source);
|
||||
|
||||
if (!definition) {
|
||||
console.log('TEST');
|
||||
log.warn('No source definition provided. Exiting early.');
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -271,7 +285,10 @@ class MeasurementService {
|
||||
/* Assign measurement source instance */
|
||||
measurement.source = source;
|
||||
} catch (error) {
|
||||
log.warn(`Failed to map '${sourceInfo}' measurement for definition ${definition}:`, error.message);
|
||||
log.warn(
|
||||
`Failed to map '${sourceInfo}' measurement for definition ${definition}:`,
|
||||
error.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -295,13 +312,24 @@ class MeasurementService {
|
||||
};
|
||||
|
||||
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._broadcastChange(this.EVENTS.MEASUREMENT_UPDATED, source, newMeasurement);
|
||||
this._broadcastChange(
|
||||
this.EVENTS.MEASUREMENT_UPDATED,
|
||||
source,
|
||||
newMeasurement
|
||||
);
|
||||
} else {
|
||||
log.info(`Measurement added.`, newMeasurement);
|
||||
this.measurements[internalId] = newMeasurement;
|
||||
this._broadcastChange(this.EVENTS.MEASUREMENT_ADDED, source, newMeasurement);
|
||||
this._broadcastChange(
|
||||
this.EVENTS.MEASUREMENT_ADDED,
|
||||
source,
|
||||
newMeasurement
|
||||
);
|
||||
}
|
||||
|
||||
return newMeasurement.id;
|
||||
|
||||
@ -26,8 +26,8 @@ describe('MeasurementService.js', () => {
|
||||
measurementData: {},
|
||||
};
|
||||
measurement = {
|
||||
sopInstanceUID: '123',
|
||||
frameOfReferenceUID: '1234',
|
||||
SOPInstanceUID: '123',
|
||||
FrameOfReferenceUID: '1234',
|
||||
referenceSeriesUID: '12345',
|
||||
label: 'Label',
|
||||
description: 'Description',
|
||||
@ -203,7 +203,6 @@ describe('MeasurementService.js', () => {
|
||||
const measurements = measurementService.getMeasurements();
|
||||
|
||||
expect(measurements.length).toEqual(2);
|
||||
expect(measurements.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
@ -278,7 +277,9 @@ describe('MeasurementService.js', () => {
|
||||
|
||||
/* Add new measurement */
|
||||
source.addOrUpdate(definition, newMeasurement);
|
||||
const savedMeasurement = measurementService.getMeasurement(newMeasurement.id);
|
||||
const savedMeasurement = measurementService.getMeasurement(
|
||||
newMeasurement.id
|
||||
);
|
||||
|
||||
/* Clear dynamic data */
|
||||
delete newMeasurement.modifiedTimestamp;
|
||||
|
||||
@ -12,12 +12,12 @@ const INFO = Symbol('INFO');
|
||||
export default function getSeriesInfo(series) {
|
||||
let info = series[INFO];
|
||||
if (!info) {
|
||||
const modality = DICOMWeb.getString(series['00080060'], '').toUpperCase();
|
||||
const Modality = DICOMWeb.getString(series['00080060'], '').toUpperCase();
|
||||
info = Object.freeze({
|
||||
modality,
|
||||
isLowPriority: isLowPriorityModality(modality),
|
||||
seriesInstanceUid: DICOMWeb.getString(series['0020000E']),
|
||||
seriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0,
|
||||
Modality,
|
||||
isLowPriority: isLowPriorityModality(Modality),
|
||||
SeriesInstanceUID: DICOMWeb.getString(series['0020000E']),
|
||||
SeriesNumber: DICOMWeb.getNumber(series['00200011'], 0) || 0,
|
||||
});
|
||||
series[INFO] = info;
|
||||
}
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
* Overridable namespace to allow getting study boxes data externally.
|
||||
*
|
||||
* 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:
|
||||
* - studyInstanceUid {String}: copy of studyInformation.studyInstanceUid
|
||||
* - modalities {String}: 2 uppercase letters for each modality split by any non-alphabetical char(s)
|
||||
* - studyDate {String}: date formatted as YYYYMMDD
|
||||
* - studyDescription {String}: study description string
|
||||
* - StudyInstanceUID {String}: copy of studyInformation.StudyInstanceUID
|
||||
* - modalities {String}: 2 uppercase letters for each Modality split by any non-alphabetical char(s)
|
||||
* - StudyDate {String}: date formatted as YYYYMMDD
|
||||
* - StudyDescription {String}: study description string
|
||||
*/
|
||||
// TODO: What is this for?
|
||||
const getStudyBoxData = false;
|
||||
|
||||
@ -8,23 +8,23 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata';
|
||||
* and waits for all of the results to be returned.
|
||||
*
|
||||
* @param {Object} server Object with server configuration parameters
|
||||
* @param {Array} studyInstanceUids The UIDs of the Studies to be retrieved
|
||||
* @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export default function retrieveStudiesMetadata(
|
||||
server,
|
||||
studyInstanceUids,
|
||||
studyInstanceUIDs,
|
||||
filters
|
||||
) {
|
||||
// Create an empty array to store the Promises for each metaData retrieval call
|
||||
const promises = [];
|
||||
|
||||
// Loop through the array of studyInstanceUids
|
||||
studyInstanceUids.forEach(function(studyInstanceUid) {
|
||||
// Loop through the array of studyInstanceUIDs
|
||||
studyInstanceUIDs.forEach(function(StudyInstanceUID) {
|
||||
// Send the call and resolve or reject the related promise based on its outcome
|
||||
const promise = retrieveStudyMetadata(server, studyInstanceUid, filters);
|
||||
const promise = retrieveStudyMetadata(server, StudyInstanceUID, filters);
|
||||
|
||||
// Add the current promise to the array of promises
|
||||
promises.push(promise);
|
||||
|
||||
@ -8,12 +8,12 @@ const StudyMetaDataPromises = new Map();
|
||||
* Retrieves study metadata
|
||||
*
|
||||
* @param {Object} server Object with server configuration parameters
|
||||
* @param {string} studyInstanceUid The UID of the Study to be retrieved
|
||||
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export function retrieveStudyMetadata(server, studyInstanceUid, filters) {
|
||||
export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
|
||||
// and further requests for that metadata will always fail. On failure, we probably need to remove the
|
||||
// corresponding promise from the "StudyMetaDataPromises" map...
|
||||
@ -21,26 +21,26 @@ export function retrieveStudyMetadata(server, studyInstanceUid, filters) {
|
||||
if (!server) {
|
||||
throw new Error(`${moduleName}: Required 'server' parameter not provided.`);
|
||||
}
|
||||
if (!studyInstanceUid) {
|
||||
if (!StudyInstanceUID) {
|
||||
throw new Error(
|
||||
`${moduleName}: Required 'studyInstanceUid' parameter not provided.`
|
||||
`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`
|
||||
);
|
||||
}
|
||||
|
||||
// Already waiting on result? Return cached promise
|
||||
if (StudyMetaDataPromises.has(studyInstanceUid)) {
|
||||
return StudyMetaDataPromises.get(studyInstanceUid);
|
||||
if (StudyMetaDataPromises.has(StudyInstanceUID)) {
|
||||
return StudyMetaDataPromises.get(StudyInstanceUID);
|
||||
}
|
||||
|
||||
// Create a promise to handle the data retrieval
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
RetrieveMetadata(server, studyInstanceUid, filters).then(function(data) {
|
||||
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
|
||||
// Store the promise in cache
|
||||
StudyMetaDataPromises.set(studyInstanceUid, promise);
|
||||
StudyMetaDataPromises.set(StudyInstanceUID, promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
@ -49,11 +49,11 @@ export function retrieveStudyMetadata(server, studyInstanceUid, filters) {
|
||||
* Delete the cached study metadata retrieval promise to ensure that the browser will
|
||||
* re-retrieve the study metadata when it is next requested
|
||||
*
|
||||
* @param {String} studyInstanceUid The UID of the Study to be removed from cache
|
||||
* @param {String} StudyInstanceUID The UID of the Study to be removed from cache
|
||||
*
|
||||
*/
|
||||
export function deleteStudyMetadataPromise(studyInstanceUid) {
|
||||
if (StudyMetaDataPromises.has(studyInstanceUid)) {
|
||||
StudyMetaDataPromises.delete(studyInstanceUid);
|
||||
export function deleteStudyMetadataPromise(StudyInstanceUID) {
|
||||
if (StudyMetaDataPromises.has(StudyInstanceUID)) {
|
||||
StudyMetaDataPromises.delete(StudyInstanceUID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,12 @@ describe('retrieveStudyMetadata.js', () => {
|
||||
expect(callWithNoServer).toThrow(Error);
|
||||
});
|
||||
|
||||
it('throws an exception if no studyInstanceUid parameter is provided', () => {
|
||||
const callWithNoStudyInstanceUid = () => {
|
||||
it('throws an exception if no StudyInstanceUID parameter is provided', () => {
|
||||
const callWithNoStudyInstanceUID = () => {
|
||||
retrieveStudyMetadata(fakeDicomWebServer, null);
|
||||
};
|
||||
|
||||
expect(callWithNoStudyInstanceUid).toThrow(Error);
|
||||
expect(callWithNoStudyInstanceUID).toThrow(Error);
|
||||
});
|
||||
|
||||
it('caches and returns the same promise for identical studyInstanceUIDs', () => {
|
||||
|
||||
@ -6,33 +6,33 @@ import { api } from 'dicomweb-client';
|
||||
* an array of series that are present in the study
|
||||
*
|
||||
* @param server The DICOM server
|
||||
* @param studyInstanceUid
|
||||
* @param StudyInstanceUID
|
||||
* @param resultData
|
||||
* @returns {Array} Series List
|
||||
*/
|
||||
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
function resultDataToStudyMetadata(server, StudyInstanceUID, resultData) {
|
||||
const seriesMap = {};
|
||||
const seriesList = [];
|
||||
const series = [];
|
||||
|
||||
resultData.forEach(function(instance) {
|
||||
// Use seriesMap to cache series data
|
||||
// If the series instance UID has already been used to
|
||||
// process series data, continue using that series
|
||||
var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
var SeriesInstanceUID = DICOMWeb.getString(instance['0020000E']);
|
||||
var series = seriesMap[SeriesInstanceUID];
|
||||
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: DICOMWeb.getString(instance['00200011']),
|
||||
SeriesInstanceUID: SeriesInstanceUID,
|
||||
SeriesNumber: DICOMWeb.getString(instance['00200011']),
|
||||
instances: [],
|
||||
};
|
||||
|
||||
// Save this data in the seriesMap cache variable
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
seriesMap[SeriesInstanceUID] = series;
|
||||
series.push(series);
|
||||
}
|
||||
|
||||
// The uri for the dicomweb
|
||||
@ -44,36 +44,36 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
|
||||
// manually create a WADO-URI from the UIDs
|
||||
// NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured?
|
||||
var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
|
||||
var SOPInstanceUID = DICOMWeb.getString(instance['00080018']);
|
||||
var uri =
|
||||
server.wadoUriRoot +
|
||||
'?requestType=WADO&studyUID=' +
|
||||
studyInstanceUid +
|
||||
StudyInstanceUID +
|
||||
'&seriesUID=' +
|
||||
seriesInstanceUid +
|
||||
SeriesInstanceUID +
|
||||
'&objectUID=' +
|
||||
sopInstanceUid +
|
||||
SOPInstanceUID +
|
||||
'&contentType=application%2Fdicom';
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
SOPClassUID: DICOMWeb.getString(instance['00080016']),
|
||||
SOPInstanceUID: SOPInstanceUID,
|
||||
uri: uri,
|
||||
instanceNumber: DICOMWeb.getString(instance['00200013']),
|
||||
InstanceNumber: DICOMWeb.getString(instance['00200013']),
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of instances using a QIDO call
|
||||
* @param server
|
||||
* @param studyInstanceUid
|
||||
* @param StudyInstanceUID
|
||||
* @throws ECONNREFUSED
|
||||
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||
* @returns {{wadoUriRoot: String, StudyInstanceUID: String, series: Array}}
|
||||
*/
|
||||
export default function Instances(server, studyInstanceUid) {
|
||||
export default function Instances(server, StudyInstanceUID) {
|
||||
// TODO: Are we using this function anywhere?? Can we remove it?
|
||||
|
||||
const config = {
|
||||
@ -86,18 +86,14 @@ export default function Instances(server, studyInstanceUid) {
|
||||
server.qidoSupportsIncludeField
|
||||
);
|
||||
const options = {
|
||||
studyInstanceUID: studyInstanceUid,
|
||||
studyInstanceUID: StudyInstanceUID,
|
||||
};
|
||||
|
||||
return dicomWeb.searchForInstances(options).then(result => {
|
||||
return {
|
||||
wadoUriRoot: server.wadoUriRoot,
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
result.data
|
||||
),
|
||||
StudyInstanceUID: StudyInstanceUID,
|
||||
series: resultDataToStudyMetadata(server, StudyInstanceUID, result.data),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -35,11 +35,11 @@ function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) {
|
||||
].join(',');
|
||||
|
||||
const parameters = {
|
||||
PatientName: filter.patientName,
|
||||
PatientID: filter.patientId,
|
||||
AccessionNumber: filter.accessionNumber,
|
||||
StudyDescription: filter.studyDescription,
|
||||
ModalitiesInStudy: filter.modalitiesInStudy,
|
||||
PatientName: filter.PatientName,
|
||||
PatientID: filter.PatientID,
|
||||
AccessionNumber: filter.AccessionNumber,
|
||||
StudyDescription: filter.StudyDescription,
|
||||
ModalitiesInStudy: filter.ModalitiesInStudy,
|
||||
limit: filter.limit,
|
||||
offset: filter.offset,
|
||||
fuzzymatching: filter.fuzzymatching,
|
||||
@ -54,11 +54,11 @@ function getQIDOQueryParams(filter, serverSupportsQIDOIncludeField) {
|
||||
}
|
||||
|
||||
// Build the StudyInstanceUID parameter
|
||||
if (filter.studyInstanceUid) {
|
||||
let studyUids = filter.studyInstanceUid;
|
||||
studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids;
|
||||
studyUids = studyUids.replace(/[^0-9.]+/g, '\\');
|
||||
parameters.StudyInstanceUID = studyUids;
|
||||
if (filter.StudyInstanceUID) {
|
||||
let studyUIDs = filter.StudyInstanceUID;
|
||||
studyUIDs = Array.isArray(studyUIDs) ? studyUIDs.join() : studyUIDs;
|
||||
studyUIDs = studyUIDs.replace(/[^0-9.]+/g, '\\');
|
||||
parameters.StudyInstanceUID = studyUIDs;
|
||||
}
|
||||
|
||||
// Clean query params of undefined values.
|
||||
@ -85,23 +85,23 @@ function resultDataToStudies(resultData) {
|
||||
|
||||
resultData.forEach(study =>
|
||||
studies.push({
|
||||
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
|
||||
StudyInstanceUID: DICOMWeb.getString(study['0020000D']),
|
||||
// 00080005 = SpecificCharacterSet
|
||||
studyDate: DICOMWeb.getString(study['00080020']),
|
||||
studyTime: DICOMWeb.getString(study['00080030']),
|
||||
accessionNumber: DICOMWeb.getString(study['00080050']),
|
||||
StudyDate: DICOMWeb.getString(study['00080020']),
|
||||
StudyTime: DICOMWeb.getString(study['00080030']),
|
||||
AccessionNumber: DICOMWeb.getString(study['00080050']),
|
||||
referringPhysicianName: DICOMWeb.getString(study['00080090']),
|
||||
// 00081190 = URL
|
||||
patientName: DICOMWeb.getName(study['00100010']),
|
||||
patientId: DICOMWeb.getString(study['00100020']),
|
||||
patientBirthdate: DICOMWeb.getString(study['00100030']),
|
||||
PatientName: DICOMWeb.getName(study['00100010']),
|
||||
PatientID: DICOMWeb.getString(study['00100020']),
|
||||
PatientBirthdate: DICOMWeb.getString(study['00100030']),
|
||||
patientSex: DICOMWeb.getString(study['00100040']),
|
||||
studyId: DICOMWeb.getString(study['00200010']),
|
||||
numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
|
||||
numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
|
||||
studyDescription: DICOMWeb.getString(study['00081030']),
|
||||
// modality: DICOMWeb.getString(study['00080060']),
|
||||
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
|
||||
StudyDescription: DICOMWeb.getString(study['00081030']),
|
||||
// Modality: DICOMWeb.getString(study['00080060']),
|
||||
// ModalitiesInStudy: DICOMWeb.getString(study['00080061']),
|
||||
modalities: DICOMWeb.getString(
|
||||
DICOMWeb.getModalities(study['00080060'], study['00080061'])
|
||||
),
|
||||
|
||||
@ -6,12 +6,12 @@ import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync';
|
||||
* will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed.
|
||||
*
|
||||
* @param {Object} server Object with server configuration parameters
|
||||
* @param {string} studyInstanceUid The Study Instance UID of the study which needs to be loaded
|
||||
* @param {string} StudyInstanceUID The Study Instance UID of the study which needs to be loaded
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @returns {Object} A study descriptor object
|
||||
*/
|
||||
async function RetrieveMetadata(server, studyInstanceUid, filters = {}) {
|
||||
async function RetrieveMetadata(server, StudyInstanceUID, filters = {}) {
|
||||
const RetrieveMetadataLoader =
|
||||
server.enableStudyLazyLoad != false
|
||||
? RetrieveMetadataLoaderAsync
|
||||
@ -19,7 +19,7 @@ async function RetrieveMetadata(server, studyInstanceUid, filters = {}) {
|
||||
|
||||
const retrieveMetadataLoader = new RetrieveMetadataLoader(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
StudyInstanceUID,
|
||||
filters
|
||||
);
|
||||
const studyMetadata = retrieveMetadataLoader.execLoad();
|
||||
|
||||
@ -9,12 +9,12 @@ import {
|
||||
} from './studyInstanceHelpers';
|
||||
|
||||
/**
|
||||
* Map seriesList to an array of seriesInstanceUid
|
||||
* @param {Arrays} seriesList list of Series Instance UIDs
|
||||
* Map series to an array of SeriesInstanceUID
|
||||
* @param {Arrays} series list of Series Instance UIDs
|
||||
* @returns {Arrays} A list of Series Instance UIDs
|
||||
*/
|
||||
function mapStudySeries(seriesList) {
|
||||
return seriesList.map(series => getSeriesInfo(series).seriesInstanceUid);
|
||||
function mapStudySeries(series) {
|
||||
return series.map(series => getSeriesInfo(series).SeriesInstanceUID);
|
||||
}
|
||||
|
||||
function attachSeriesLoader(server, study, seriesLoader) {
|
||||
@ -107,9 +107,9 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
||||
result,
|
||||
sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria
|
||||
);
|
||||
const seriesInstanceUidsMap = mapStudySeries(seriesSorted);
|
||||
const seriesInstanceUIDsMap = mapStudySeries(seriesSorted);
|
||||
|
||||
return seriesInstanceUidsMap;
|
||||
return seriesInstanceUIDsMap;
|
||||
}
|
||||
|
||||
async load(preLoadData) {
|
||||
|
||||
@ -1,63 +1,7 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../../DICOMWeb';
|
||||
|
||||
const WADOProxy = {
|
||||
convertURL: (url, server) => {
|
||||
// TODO: Remove all WADOProxy stuff from this file
|
||||
return url;
|
||||
},
|
||||
};
|
||||
function parseFloatArray(obj) {
|
||||
const result = [];
|
||||
|
||||
if (!obj) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const objs = obj.split('\\');
|
||||
for (let i = 0; i < objs.length; i++) {
|
||||
result.push(parseFloat(objs[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple cache schema for retrieved color palettes.
|
||||
*/
|
||||
const paletteColorCache = {
|
||||
count: 0,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
|
||||
entries: {},
|
||||
isValidUID: function(paletteUID) {
|
||||
return typeof paletteUID === 'string' && paletteUID.length > 0;
|
||||
},
|
||||
get: function(paletteUID) {
|
||||
let entry = null;
|
||||
if (this.entries.hasOwnProperty(paletteUID)) {
|
||||
entry = this.entries[paletteUID];
|
||||
// check how the entry is...
|
||||
if (Date.now() - entry.time > this.maxAge) {
|
||||
// entry is too old... remove entry.
|
||||
delete this.entries[paletteUID];
|
||||
this.count--;
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
},
|
||||
add: function(entry) {
|
||||
if (this.isValidUID(entry.uid)) {
|
||||
let paletteUID = entry.uid;
|
||||
if (this.entries.hasOwnProperty(paletteUID) !== true) {
|
||||
this.count++; // increment cache entry count...
|
||||
}
|
||||
entry.time = Date.now();
|
||||
this.entries[paletteUID] = entry;
|
||||
// @TODO: Add logic to get rid of old entries and reduce memory usage...
|
||||
}
|
||||
},
|
||||
};
|
||||
import metadataProvider from '../../../classes/MetadataProvider';
|
||||
import getWADORSImageId from '../../../utils/getWADORSImageId';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
|
||||
/**
|
||||
* Create a plain JS object that describes a study (a study descriptor object)
|
||||
@ -67,47 +11,47 @@ const paletteColorCache = {
|
||||
function createStudy(server, aSopInstance) {
|
||||
// TODO: Pass a reference ID to the server instead of including the URLs here
|
||||
return {
|
||||
seriesList: [],
|
||||
series: [],
|
||||
seriesMap: Object.create(null),
|
||||
seriesLoader: null,
|
||||
wadoUriRoot: server.wadoUriRoot,
|
||||
wadoRoot: server.wadoRoot,
|
||||
qidoRoot: server.qidoRoot,
|
||||
patientName: DICOMWeb.getName(aSopInstance['00100010']),
|
||||
patientId: DICOMWeb.getString(aSopInstance['00100020']),
|
||||
patientAge: DICOMWeb.getNumber(aSopInstance['00101010']),
|
||||
patientSize: DICOMWeb.getNumber(aSopInstance['00101020']),
|
||||
patientWeight: DICOMWeb.getNumber(aSopInstance['00101030']),
|
||||
accessionNumber: DICOMWeb.getString(aSopInstance['00080050']),
|
||||
studyDate: DICOMWeb.getString(aSopInstance['00080020']),
|
||||
modalities: DICOMWeb.getString(aSopInstance['00080061']),
|
||||
studyDescription: DICOMWeb.getString(aSopInstance['00081030']),
|
||||
imageCount: DICOMWeb.getString(aSopInstance['00201208']),
|
||||
studyInstanceUid: DICOMWeb.getString(aSopInstance['0020000D']),
|
||||
institutionName: DICOMWeb.getString(aSopInstance['00080080']),
|
||||
PatientName: DICOMWeb.getName(aSopInstance['00100010']),
|
||||
PatientID: DICOMWeb.getString(aSopInstance['00100020']),
|
||||
PatientAge: DICOMWeb.getNumber(aSopInstance['00101010']),
|
||||
PatientSize: DICOMWeb.getNumber(aSopInstance['00101020']),
|
||||
PatientWeight: DICOMWeb.getNumber(aSopInstance['00101030']),
|
||||
AccessionNumber: DICOMWeb.getString(aSopInstance['00080050']),
|
||||
StudyDate: DICOMWeb.getString(aSopInstance['00080020']),
|
||||
modalities: DICOMWeb.getString(aSopInstance['00080061']), // TODO -> Rename this.. it'll take a while to not mess this one up.
|
||||
StudyDescription: DICOMWeb.getString(aSopInstance['00081030']),
|
||||
NumberOfStudyRelatedInstances: DICOMWeb.getString(aSopInstance['00201208']),
|
||||
StudyInstanceUID: DICOMWeb.getString(aSopInstance['0020000D']),
|
||||
InstitutionName: DICOMWeb.getString(aSopInstance['00080080']),
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a WADO url for an instance
|
||||
*
|
||||
* @param studyInstanceUid
|
||||
* @param seriesInstanceUid
|
||||
* @param sopInstanceUid
|
||||
* @param StudyInstanceUID
|
||||
* @param SeriesInstanceUID
|
||||
* @param SOPInstanceUID
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildInstanceWadoUrl(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
) {
|
||||
// TODO: This can be removed, since DICOMWebClient has the same function. Not urgent, though
|
||||
const params = [];
|
||||
|
||||
params.push('requestType=WADO');
|
||||
params.push(`studyUID=${studyInstanceUid}`);
|
||||
params.push(`seriesUID=${seriesInstanceUid}`);
|
||||
params.push(`objectUID=${sopInstanceUid}`);
|
||||
params.push(`studyUID=${StudyInstanceUID}`);
|
||||
params.push(`seriesUID=${SeriesInstanceUID}`);
|
||||
params.push(`objectUID=${SOPInstanceUID}`);
|
||||
params.push('contentType=application/dicom');
|
||||
params.push('transferSyntax=*');
|
||||
|
||||
@ -118,342 +62,120 @@ function buildInstanceWadoUrl(
|
||||
|
||||
function buildInstanceWadoRsUri(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
) {
|
||||
return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}`;
|
||||
return `${server.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}`;
|
||||
}
|
||||
|
||||
function buildInstanceFrameWadoRsUri(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frame
|
||||
) {
|
||||
const baseWadoRsUri = buildInstanceWadoRsUri(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
frame = frame != null || 1;
|
||||
|
||||
return `${baseWadoRsUri}/frames/${frame}`;
|
||||
}
|
||||
|
||||
function getFrameIncrementPointer(element) {
|
||||
const frameIncrementPointerNames = {
|
||||
'00181065': 'frameTimeVector',
|
||||
'00181063': 'frameTime',
|
||||
};
|
||||
|
||||
if (!element || !element.Value || !element.Value.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = element.Value[0];
|
||||
return frameIncrementPointerNames[value];
|
||||
}
|
||||
|
||||
function getRadiopharmaceuticalInfo(instance) {
|
||||
const modality = DICOMWeb.getString(instance['00080060']);
|
||||
|
||||
if (modality !== 'PT') {
|
||||
return;
|
||||
}
|
||||
|
||||
const radiopharmaceuticalInfo = instance['00540016'];
|
||||
if (
|
||||
radiopharmaceuticalInfo === undefined ||
|
||||
!radiopharmaceuticalInfo.Value ||
|
||||
!radiopharmaceuticalInfo.Value.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0];
|
||||
return {
|
||||
radiopharmaceuticalStartTime: DICOMWeb.getString(
|
||||
firstPetRadiopharmaceuticalInfo['00181072']
|
||||
),
|
||||
radionuclideTotalDose: DICOMWeb.getNumber(
|
||||
firstPetRadiopharmaceuticalInfo['00181074']
|
||||
),
|
||||
radionuclideHalfLife: DICOMWeb.getNumber(
|
||||
firstPetRadiopharmaceuticalInfo['00181075']
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the SourceImageSequence, if it exists, in order
|
||||
* to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
|
||||
* is used to refer to this image in any accompanying DICOM-SR documents.
|
||||
*
|
||||
* @param instance
|
||||
* @returns {String} The ReferenceSOPInstanceUID
|
||||
*/
|
||||
function getSourceImageInstanceUid(instance) {
|
||||
// TODO= Parse the whole Source Image Sequence
|
||||
// This is a really poor workaround for now.
|
||||
// Later we should probably parse the whole sequence.
|
||||
var SourceImageSequence = instance['00082112'];
|
||||
if (
|
||||
SourceImageSequence &&
|
||||
SourceImageSequence.Value &&
|
||||
SourceImageSequence.Value.length &&
|
||||
SourceImageSequence.Value[0]['00081155'].Value
|
||||
) {
|
||||
return SourceImageSequence.Value[0]['00081155'].Value[0];
|
||||
}
|
||||
}
|
||||
|
||||
async function makeSOPInstance(server, study, instance) {
|
||||
const { studyInstanceUid } = study;
|
||||
const seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
|
||||
let series = study.seriesMap[seriesInstanceUid];
|
||||
const naturalizedInstance = await metadataProvider.addInstance(instance, {
|
||||
server,
|
||||
});
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = naturalizedInstance;
|
||||
|
||||
let series = study.seriesMap[SeriesInstanceUID];
|
||||
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid,
|
||||
seriesDescription: DICOMWeb.getString(instance['0008103E']),
|
||||
modality: DICOMWeb.getString(instance['00080060']),
|
||||
seriesNumber: DICOMWeb.getNumber(instance['00200011']),
|
||||
seriesDate: DICOMWeb.getString(instance['00080021']),
|
||||
seriesTime: DICOMWeb.getString(instance['00080031']),
|
||||
SeriesInstanceUID,
|
||||
SeriesDescription: naturalizedInstance.SeriesDescription,
|
||||
Modality: naturalizedInstance.Modality,
|
||||
SeriesNumber: naturalizedInstance.SeriesNumber,
|
||||
SeriesDate: naturalizedInstance.SeriesDate,
|
||||
SeriesTime: naturalizedInstance.SeriesTime,
|
||||
instances: [],
|
||||
};
|
||||
study.seriesMap[seriesInstanceUid] = series;
|
||||
study.seriesList.push(series);
|
||||
study.seriesMap[SeriesInstanceUID] = series;
|
||||
study.series.push(series);
|
||||
}
|
||||
|
||||
const sopInstanceUid = DICOMWeb.getString(instance['00080018']);
|
||||
const wadouri = buildInstanceWadoUrl(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
const baseWadoRsUri = buildInstanceWadoRsUri(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
const wadorsuri = buildInstanceFrameWadoRsUri(
|
||||
server,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID
|
||||
);
|
||||
|
||||
const sopInstance = {
|
||||
imageType: DICOMWeb.getString(instance['00080008']),
|
||||
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
||||
modality: DICOMWeb.getString(instance['00080060']),
|
||||
sopInstanceUid,
|
||||
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
|
||||
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
|
||||
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
|
||||
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
|
||||
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
|
||||
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
|
||||
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
|
||||
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
|
||||
rows: DICOMWeb.getNumber(instance['00280010']),
|
||||
columns: DICOMWeb.getNumber(instance['00280011']),
|
||||
pixelSpacing: DICOMWeb.getString(instance['00280030']),
|
||||
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
|
||||
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
|
||||
bitsStored: DICOMWeb.getNumber(instance['00280101']),
|
||||
highBit: DICOMWeb.getNumber(instance['00280102']),
|
||||
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
|
||||
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
|
||||
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
|
||||
windowCenter: DICOMWeb.getString(instance['00281050']),
|
||||
windowWidth: DICOMWeb.getString(instance['00281051']),
|
||||
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
|
||||
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
|
||||
rescaleType: DICOMWeb.getNumber(instance['00281054']),
|
||||
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
||||
laterality: DICOMWeb.getString(instance['00200062']),
|
||||
viewPosition: DICOMWeb.getString(instance['00185101']),
|
||||
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
|
||||
numberOfFrames: DICOMWeb.getNumber(instance['00280008']),
|
||||
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
|
||||
frameTime: DICOMWeb.getNumber(instance['00181063']),
|
||||
frameTimeVector: parseFloatArray(DICOMWeb.getString(instance['00181065'])),
|
||||
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
|
||||
spacingBetweenSlices: DICOMWeb.getString(instance['00180088']),
|
||||
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
|
||||
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
||||
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
||||
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
||||
echoNumber: DICOMWeb.getString(instance['00180086']),
|
||||
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
|
||||
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
|
||||
baseWadoRsUri: baseWadoRsUri,
|
||||
wadouri: WADOProxy.convertURL(wadouri, server),
|
||||
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
|
||||
metadata: naturalizedInstance,
|
||||
baseWadoRsUri,
|
||||
wadouri,
|
||||
wadorsuri,
|
||||
wadoRoot: server.wadoRoot,
|
||||
imageRendering: server.imageRendering,
|
||||
thumbnailRendering: server.thumbnailRendering,
|
||||
};
|
||||
|
||||
// Get additional information if the instance uses "PALETTE COLOR" photometric interpretation
|
||||
if (sopInstance.photometricInterpretation === 'PALETTE COLOR') {
|
||||
const redPaletteColorLookupTableDescriptor = parseFloatArray(
|
||||
DICOMWeb.getString(instance['00281101'])
|
||||
);
|
||||
const greenPaletteColorLookupTableDescriptor = parseFloatArray(
|
||||
DICOMWeb.getString(instance['00281102'])
|
||||
);
|
||||
const bluePaletteColorLookupTableDescriptor = parseFloatArray(
|
||||
DICOMWeb.getString(instance['00281103'])
|
||||
);
|
||||
const palettes = await getPaletteColors(
|
||||
server,
|
||||
instance,
|
||||
redPaletteColorLookupTableDescriptor
|
||||
);
|
||||
|
||||
if (palettes) {
|
||||
if (palettes.uid) {
|
||||
sopInstance.paletteColorLookupTableUID = palettes.uid;
|
||||
}
|
||||
|
||||
sopInstance.redPaletteColorLookupTableData = palettes.red;
|
||||
sopInstance.greenPaletteColorLookupTableData = palettes.green;
|
||||
sopInstance.bluePaletteColorLookupTableData = palettes.blue;
|
||||
sopInstance.redPaletteColorLookupTableDescriptor = redPaletteColorLookupTableDescriptor;
|
||||
sopInstance.greenPaletteColorLookupTableDescriptor = greenPaletteColorLookupTableDescriptor;
|
||||
sopInstance.bluePaletteColorLookupTableDescriptor = bluePaletteColorLookupTableDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
series.instances.push(sopInstance);
|
||||
return sopInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert String to ArrayBuffer
|
||||
*
|
||||
* @param {String} str Input String
|
||||
* @return {ArrayBuffer} Output converted ArrayBuffer
|
||||
*/
|
||||
function str2ab(str) {
|
||||
const strLen = str.length;
|
||||
const bytes = new Uint8Array(strLen);
|
||||
if (
|
||||
sopInstance.thumbnailRendering === 'wadors' ||
|
||||
sopInstance.imageRendering === 'wadors'
|
||||
) {
|
||||
// If using WADO-RS for either images or thumbnails,
|
||||
// Need to add this to cornerstoneWADOImageLoader's provider
|
||||
// (it won't be hit on cornerstone.metaData.get, but cornerstoneWADOImageLoader
|
||||
// will cry if you don't add data to cornerstoneWADOImageLoader.wadors.metaDataManager).
|
||||
|
||||
for (let i = 0; i < strLen; i++) {
|
||||
bytes[i] = str.charCodeAt(i);
|
||||
}
|
||||
const wadoRSMetadata = Object.assign(instance);
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
if (sopInstance.NumberOfFrames) {
|
||||
for (let i = 0; i < sopInstance.NumberOfFrames; i++) {
|
||||
const wadorsImageId = getWADORSImageId(sopInstance, i);
|
||||
|
||||
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
||||
const numLutEntries = lutDescriptor[0];
|
||||
const bits = lutDescriptor[2];
|
||||
|
||||
const readUInt16 = (byteArray, position) => {
|
||||
return byteArray[position] + byteArray[position + 1] * 256;
|
||||
};
|
||||
|
||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||
const byteArray = new Uint8Array(arraybuffer);
|
||||
const lut = [];
|
||||
|
||||
if (bits === 16) {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = readUInt16(byteArray, i * 2);
|
||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(
|
||||
wadorsImageId,
|
||||
wadoRSMetadata
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
}
|
||||
const wadorsImageId = getWADORSImageId(sopInstance);
|
||||
|
||||
return lut;
|
||||
};
|
||||
|
||||
if (instance[tag].BulkDataURI) {
|
||||
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
return dicomWeb
|
||||
.retrieveBulkData(options)
|
||||
.then(result => result[0])
|
||||
.then(arrayBufferToPaletteColorLUT);
|
||||
} else if (instance[tag].InlineBinary) {
|
||||
const inlineBinaryData = atob(instance[tag].InlineBinary);
|
||||
const arraybuf = str2ab(inlineBinaryData);
|
||||
|
||||
return arrayBufferToPaletteColorLUT(arraybuf);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
|
||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(
|
||||
wadorsImageId,
|
||||
wadoRSMetadata
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch palette colors for instances with "PALETTE COLOR" photometricInterpretation.
|
||||
*
|
||||
* @param server {Object} Current server;
|
||||
* @param instance {Object} The retrieved instance metadata;
|
||||
* @returns {String} The ReferenceSOPInstanceUID
|
||||
*/
|
||||
async function getPaletteColors(server, instance, lutDescriptor) {
|
||||
let paletteUID = DICOMWeb.getString(instance['00281199']);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let entry;
|
||||
if (paletteColorCache.isValidUID(paletteUID)) {
|
||||
entry = paletteColorCache.get(paletteUID);
|
||||
|
||||
if (entry) {
|
||||
return resolve(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// no entry in cache... Fetch remote data.
|
||||
const r = getPaletteColor(server, instance, '00281201', lutDescriptor);
|
||||
const g = getPaletteColor(server, instance, '00281202', lutDescriptor);
|
||||
const b = getPaletteColor(server, instance, '00281203', lutDescriptor);
|
||||
|
||||
const promises = [r, g, b];
|
||||
|
||||
Promise.all(promises).then(args => {
|
||||
entry = {
|
||||
red: args[0],
|
||||
green: args[1],
|
||||
blue: args[2],
|
||||
};
|
||||
|
||||
// when paletteUID is present, the entry can be cached...
|
||||
entry.uid = paletteUID;
|
||||
paletteColorCache.add(entry);
|
||||
|
||||
resolve(entry);
|
||||
});
|
||||
});
|
||||
return sopInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,16 +15,16 @@ function seriesInfoSortingCriteria(firstSeries, secondSeries) {
|
||||
if (a.isLowPriority && !b.isLowPriority) {
|
||||
return 1;
|
||||
}
|
||||
return a.seriesNumber - b.seriesNumber;
|
||||
return a.SeriesNumber - b.SeriesNumber;
|
||||
}
|
||||
|
||||
const seriesSortCriteria = {
|
||||
default: (a, b) => a.seriesNumber - b.seriesNumber,
|
||||
default: (a, b) => a.SeriesNumber - b.SeriesNumber,
|
||||
seriesInfoSortingCriteria,
|
||||
};
|
||||
|
||||
const instancesSortCriteria = {
|
||||
default: (a, b) => a.instanceNumber - b.instanceNumber,
|
||||
default: (a, b) => a.InstanceNumber - b.InstanceNumber,
|
||||
};
|
||||
|
||||
const sortingCriteria = {
|
||||
@ -33,18 +33,18 @@ const sortingCriteria = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts given seriesList (given param is modified)
|
||||
* Sorts given series (given param is modified)
|
||||
* The default criteria is based on series number in ascending order.
|
||||
*
|
||||
* @param {Array} seriesList List of series
|
||||
* @param {Array} series List of series
|
||||
* @param {function} seriesSortingCriteria method for sorting
|
||||
* @returns {Array} sorted seriesList object
|
||||
* @returns {Array} sorted series object
|
||||
*/
|
||||
const sortStudySeries = (
|
||||
seriesList,
|
||||
series,
|
||||
seriesSortingCriteria = seriesSortCriteria.default
|
||||
) => {
|
||||
return seriesList.sort(seriesSortingCriteria);
|
||||
return series.sort(seriesSortingCriteria);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -78,14 +78,14 @@ export default function sortStudy(
|
||||
seriesSortingCriteria = seriesSortCriteria.default,
|
||||
instancesSortingCriteria = instancesSortCriteria.default
|
||||
) {
|
||||
if (!study || !study.seriesList) {
|
||||
if (!study || !study.series) {
|
||||
throw new Error('Insufficient study data was provided to sortStudy');
|
||||
}
|
||||
|
||||
sortStudySeries(study.seriesList, seriesSortingCriteria);
|
||||
sortStudySeries(study.series, seriesSortingCriteria);
|
||||
|
||||
if (deepSort) {
|
||||
study.seriesList.forEach(series => {
|
||||
study.series.forEach(series => {
|
||||
sortStudyInstances(series.instances, instancesSortingCriteria);
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
||||
import OHIFError from '../classes/OHIFError.js';
|
||||
import getImageId from './getImageId';
|
||||
import metadataProvider from '../classes/MetadataProvider.js';
|
||||
|
||||
let stackMap = {};
|
||||
let configuration = {};
|
||||
let stackManagerMetaDataProvider;
|
||||
const stackUpdatedCallbacks = [];
|
||||
|
||||
/**
|
||||
@ -15,13 +15,7 @@ const stackUpdatedCallbacks = [];
|
||||
* @param {Object} displaySet The set of images to make the stack from
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
function createAndAddStack(
|
||||
stackMap,
|
||||
study,
|
||||
displaySet,
|
||||
stackUpdatedCallbacks,
|
||||
metadataProvider
|
||||
) {
|
||||
function createAndAddStack(stackMap, study, displaySet, stackUpdatedCallbacks) {
|
||||
const images = displaySet.images;
|
||||
if (!images) {
|
||||
return;
|
||||
@ -41,31 +35,56 @@ function createAndAddStack(
|
||||
imageIndex: imageIndex + 1,
|
||||
};
|
||||
|
||||
const numberOfFrames = image.numberOfFrames;
|
||||
if (numberOfFrames > 1) {
|
||||
for (let i = 0; i < numberOfFrames; i++) {
|
||||
const NumberOfFrames = image.NumberOfFrames;
|
||||
|
||||
if (NumberOfFrames > 1) {
|
||||
for (let i = 0; i < NumberOfFrames; i++) {
|
||||
metaData.frameNumber = i;
|
||||
imageId = getImageId(image, i);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = instance.getData().metadata;
|
||||
|
||||
metadataProvider.addImageIdToUIDs(imageId, {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
metaData.frameNumber = 1;
|
||||
imageId = getImageId(image);
|
||||
imageIds.push(imageId);
|
||||
metadataProvider.addMetadata(imageId, metaData);
|
||||
|
||||
const naturalizedInstance = instance.getData().metadata;
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = naturalizedInstance;
|
||||
|
||||
metadataProvider.addImageIdToUIDs(imageId, {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const stack = {
|
||||
studyInstanceUid: study.studyInstanceUid,
|
||||
displaySetInstanceUid: displaySet.displaySetInstanceUid,
|
||||
StudyInstanceUID: study.StudyInstanceUID,
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
imageIds,
|
||||
frameRate: displaySet.frameRate,
|
||||
isClip: displaySet.isClip,
|
||||
};
|
||||
|
||||
stackMap[displaySet.displaySetInstanceUid] = stack;
|
||||
stackMap[displaySet.displaySetInstanceUID] = stack;
|
||||
|
||||
return stack;
|
||||
}
|
||||
@ -80,9 +99,6 @@ configuration = {
|
||||
* come in, you can register a callback with addStackUpdatedCallback.
|
||||
*/
|
||||
const StackManager = {
|
||||
setMetadataProvider(provider) {
|
||||
stackManagerMetaDataProvider = provider;
|
||||
},
|
||||
/**
|
||||
* Removes all current stacks
|
||||
*/
|
||||
@ -96,27 +112,20 @@ const StackManager = {
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
makeAndAddStack(study, displaySet) {
|
||||
if (!stackManagerMetaDataProvider) {
|
||||
throw new Error(
|
||||
'Please call StackManager.setMetadataProvider(provider) first.'
|
||||
);
|
||||
}
|
||||
|
||||
return configuration.createAndAddStack(
|
||||
stackMap,
|
||||
study,
|
||||
displaySet,
|
||||
stackUpdatedCallbacks,
|
||||
stackManagerMetaDataProvider
|
||||
stackUpdatedCallbacks
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Find a stack from the currently created stacks.
|
||||
* @param displaySetInstanceUid The UID of the stack to find.
|
||||
* @param displaySetInstanceUID The UID of the stack to find.
|
||||
* @returns {*} undefined if not found, otherwise the stack object is returned.
|
||||
*/
|
||||
findStack(displaySetInstanceUid) {
|
||||
return stackMap[displaySetInstanceUid];
|
||||
findStack(displaySetInstanceUID) {
|
||||
return stackMap[displaySetInstanceUID];
|
||||
},
|
||||
/**
|
||||
* Find a stack or reate one if it has not been created yet
|
||||
@ -125,7 +134,7 @@ const StackManager = {
|
||||
* @return {Array} Array with image IDs
|
||||
*/
|
||||
findOrCreateStack(study, displaySet) {
|
||||
let stack = this.findStack(displaySet.displaySetInstanceUid);
|
||||
let stack = this.findStack(displaySet.displaySetInstanceUID);
|
||||
|
||||
if (!stack || !stack.imageIds) {
|
||||
stack = this.makeAndAddStack(study, displaySet);
|
||||
@ -134,9 +143,9 @@ const StackManager = {
|
||||
return stack;
|
||||
},
|
||||
/**
|
||||
* Gets the underlying map of displaySetInstanceUid to stack object.
|
||||
* Gets the underlying map of displaySetInstanceUID to stack object.
|
||||
* WARNING: Do not change this object. It directly affects the manager.
|
||||
* @returns {{}} map of displaySetInstanceUid -> stack.
|
||||
* @returns {{}} map of displaySetInstanceUID -> stack.
|
||||
*/
|
||||
getAllStacks() {
|
||||
return stackMap;
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
// {
|
||||
// getInstanceCount: jest.fn().mockReturnValue(1),
|
||||
// getData: jest.fn().mockReturnValue({
|
||||
// seriesDate: '2019-06-04',
|
||||
// SeriesDate: '2019-06-04',
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// getInstanceCount: jest.fn().mockReturnValue(1),
|
||||
// getData: jest.fn().mockReturnValue({
|
||||
// seriesDate: '2018-06-04',
|
||||
// SeriesDate: '2018-06-04',
|
||||
// }),
|
||||
// },
|
||||
// ];
|
||||
@ -25,7 +25,7 @@
|
||||
// getStudyInstanceUID: jest.fn(),
|
||||
// };
|
||||
|
||||
// it('sorts displaySets by seriesNumber, then by seriesDate', () => {
|
||||
// it('sorts displaySets by SeriesNumber, then by SeriesDate', () => {
|
||||
// const displaySets = createStacks(studyMetadata);
|
||||
|
||||
// expect(displaySets.length).toBe(2);
|
||||
|
||||
@ -13,15 +13,15 @@ const getImageId = imageObj => {
|
||||
: imageObj.url;
|
||||
};
|
||||
|
||||
const findImageIdOnStudies = (studies, displaySetInstanceUid) => {
|
||||
const findImageIdOnStudies = (studies, displaySetInstanceUID) => {
|
||||
const study = studies.find(study => {
|
||||
const displaySet = study.displaySets.some(
|
||||
displaySet => displaySet.displaySetInstanceUid === displaySetInstanceUid
|
||||
displaySet => displaySet.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
return displaySet;
|
||||
});
|
||||
const { seriesList = [] } = study;
|
||||
const { instances = [] } = seriesList[0] || {};
|
||||
const { series = [] } = study;
|
||||
const { instances = [] } = series[0] || {};
|
||||
const instance = instances[0];
|
||||
|
||||
return getImageId(instance);
|
||||
@ -85,7 +85,7 @@ const getImageLoaderType = imageId => {
|
||||
);
|
||||
};
|
||||
|
||||
const DicomLoaderService = new (class {
|
||||
class DicomLoaderService {
|
||||
getLocalData(dataset, studies) {
|
||||
if (dataset && dataset.localFile) {
|
||||
// Use referenced imageInstance
|
||||
@ -94,7 +94,7 @@ const DicomLoaderService = new (class {
|
||||
|
||||
// or Try to get it from studies
|
||||
if (someInvalidStrings(imageId)) {
|
||||
imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUid);
|
||||
imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUID);
|
||||
}
|
||||
|
||||
if (!someInvalidStrings(imageId)) {
|
||||
@ -155,9 +155,9 @@ const DicomLoaderService = new (class {
|
||||
|
||||
getDataByDatasetType(dataset) {
|
||||
const {
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
authorizationHeaders,
|
||||
wadoRoot,
|
||||
wadoUri,
|
||||
@ -166,9 +166,9 @@ const DicomLoaderService = new (class {
|
||||
if (!someInvalidStrings(wadoRoot)) {
|
||||
return wadorsRetriever(
|
||||
wadoRoot,
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
sopInstanceUid,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
authorizationHeaders
|
||||
);
|
||||
} else if (!someInvalidStrings(wadoUri)) {
|
||||
@ -194,6 +194,8 @@ const DicomLoaderService = new (class {
|
||||
// in case of no valid loader
|
||||
throw new Error('Invalid dicom data loader');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export default DicomLoaderService;
|
||||
const dicomLoaderService = new DicomLoaderService();
|
||||
|
||||
export default dicomLoaderService;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { DICOMTagDescriptions } from './DICOMTagDescriptions';
|
||||
import ObjectPath from './objectPath';
|
||||
import StackManager from './StackManager.js';
|
||||
import absoluteUrl from './absoluteUrl';
|
||||
@ -6,7 +5,6 @@ import addServers from './addServers';
|
||||
import guid from './guid';
|
||||
import sortBy from './sortBy.js';
|
||||
import studyMetadataManager from './studyMetadataManager';
|
||||
import updateMetaDataManager from './updateMetaDataManager.js';
|
||||
import writeScript from './writeScript.js';
|
||||
import DicomLoaderService from './dicomLoaderService.js';
|
||||
import b64toBlob from './b64toBlob.js';
|
||||
@ -24,9 +22,6 @@ const utils = {
|
||||
b64toBlob,
|
||||
StackManager,
|
||||
studyMetadataManager,
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
makeCancelable,
|
||||
@ -43,9 +38,6 @@ export {
|
||||
b64toBlob,
|
||||
StackManager,
|
||||
studyMetadataManager,
|
||||
// Updates WADO-RS metaDataManager
|
||||
updateMetaDataManager,
|
||||
DICOMTagDescriptions,
|
||||
DicomLoaderService,
|
||||
urlUtil,
|
||||
makeCancelable,
|
||||
|
||||
@ -12,9 +12,6 @@ describe('Top level exports', () => {
|
||||
'b64toBlob',
|
||||
'StackManager',
|
||||
'studyMetadataManager',
|
||||
// Updates WADO-RS metaDataManager
|
||||
'updateMetaDataManager',
|
||||
'DICOMTagDescriptions',
|
||||
'DicomLoaderService',
|
||||
'urlUtil',
|
||||
'makeCancelable',
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
/**
|
||||
* Checks if a series is reconstructable to a 3D volume.
|
||||
*
|
||||
* @param {Object} series The `OHIFSeriesMetadata` object.
|
||||
* @param {Object[]} instances The `OHIFInstanceMetadata` object
|
||||
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
|
||||
*/
|
||||
export default function isDisplaySetReconstructable(series, instances) {
|
||||
// Can't reconstruct if we only have one image.
|
||||
|
||||
const modality = series._data.modality; // TODO -> Is there a better way to get this?
|
||||
const isMultiframe = instances[0].getRawValue('x00280008') > 1;
|
||||
|
||||
if (!constructableModalities.includes(modality)) {
|
||||
export default function isDisplaySetReconstructable(instances) {
|
||||
if (!instances.length) {
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
const firstInstance = instances[0].getData().metadata;
|
||||
|
||||
const Modality = firstInstance.Modality;
|
||||
const isMultiframe = firstInstance.NumberOfFrames > 1;
|
||||
|
||||
if (!constructableModalities.includes(Modality)) {
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
// Can't reconstruct if we only have one image.
|
||||
if (!isMultiframe && instances.length === 1) {
|
||||
return { values: false };
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
if (isMultiframe) {
|
||||
@ -26,34 +30,36 @@ export default function isDisplaySetReconstructable(series, instances) {
|
||||
}
|
||||
|
||||
function processMultiframe(instance) {
|
||||
//TODO: deal with multriframe checks! return true for now.
|
||||
return { value: true };
|
||||
//TODO: deal with multriframe checks! return false for now as can't reconstruct.
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
function processSingleframe(instances) {
|
||||
const firstImage = instances[0];
|
||||
const firstImageRows = firstImage.getTagValue('x00280010');
|
||||
const firstImageColumns = firstImage.getTagValue('x00280011');
|
||||
const firstImageSamplesPerPixel = firstImage.getTagValue('x00280002');
|
||||
// Note: No need to unpack iop, can compare string form.
|
||||
const firstImageOrientationPatient = firstImage.getTagValue('x00200037');
|
||||
const firstImage = instances[0].getData().metadata;
|
||||
const firstImageRows = firstImage.Rows;
|
||||
const firstImageColumns = firstImage.Columns;
|
||||
const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
|
||||
const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
|
||||
const firstImagePositionPatient = firstImage.ImagePositionPatient;
|
||||
|
||||
// Can't reconstruct if we:
|
||||
// -- Have a different dimensions within a displaySet.
|
||||
// -- Have a different number of components within a displaySet.
|
||||
// -- Have different orientations within a displaySet.
|
||||
for (let i = 1; i < instances.length; i++) {
|
||||
const instance = instances[i];
|
||||
const rows = instance.getTagValue('x00280010');
|
||||
const columns = instance.getTagValue('x00280011');
|
||||
const samplesPerPixel = instance.getTagValue('x00280002');
|
||||
const imageOrientationPatient = instance.getTagValue('x00200037');
|
||||
const instance = instances[i].getData().metadata;
|
||||
const {
|
||||
Rows,
|
||||
Columns,
|
||||
SamplesPerPixel,
|
||||
ImageOrientationPatient,
|
||||
} = instance;
|
||||
|
||||
if (
|
||||
rows !== firstImageRows ||
|
||||
columns !== firstImageColumns ||
|
||||
samplesPerPixel !== firstImageSamplesPerPixel ||
|
||||
imageOrientationPatient !== firstImageOrientationPatient
|
||||
Rows !== firstImageRows ||
|
||||
Columns !== firstImageColumns ||
|
||||
SamplesPerPixel !== firstImageSamplesPerPixel ||
|
||||
!_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient)
|
||||
) {
|
||||
return { value: false };
|
||||
}
|
||||
@ -61,28 +67,32 @@ function processSingleframe(instances) {
|
||||
|
||||
let missingFrames = 0;
|
||||
|
||||
// Check if frame spacing is approximately equal within a tolerance.
|
||||
// Check if frame spacing is approximately equal within a spacingTolerance.
|
||||
// If spacing is on a uniform grid but we are missing frames,
|
||||
// Allow reconstruction, but pass back the number of missing frames.
|
||||
if (instances.length > 2) {
|
||||
const firstIpp = _getImagePositionPatient(firstImage);
|
||||
const lastIpp = _getImagePositionPatient(instances[instances.length - 1]);
|
||||
const lastIpp = instances[instances.length - 1].getData().metadata
|
||||
.ImagePositionPatient;
|
||||
|
||||
// We can't reconstruct if we are missing imagePositionPatient values
|
||||
if (!firstIpp || !lastIpp) {
|
||||
// We can't reconstruct if we are missing ImagePositionPatient values
|
||||
if (!firstImagePositionPatient || !lastIpp) {
|
||||
return { value: false };
|
||||
}
|
||||
|
||||
const averageSpacingBetweenFrames =
|
||||
_getPerpendicularDistance(firstIpp, lastIpp) / (instances.length - 1);
|
||||
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
|
||||
(instances.length - 1);
|
||||
|
||||
let previousIpp = firstIpp;
|
||||
let previousImagePositionPatient = firstImagePositionPatient;
|
||||
|
||||
for (let i = 1; i < instances.length; i++) {
|
||||
const instance = instances[i];
|
||||
const ipp = _getImagePositionPatient(instance);
|
||||
const instance = instances[i].getData().metadata;
|
||||
const { ImagePositionPatient } = instance;
|
||||
|
||||
const spacingBetweenFrames = _getPerpendicularDistance(ipp, previousIpp);
|
||||
const spacingBetweenFrames = _getPerpendicularDistance(
|
||||
ImagePositionPatient,
|
||||
previousImagePositionPatient
|
||||
);
|
||||
const spacingIssue = _getSpacingIssue(
|
||||
spacingBetweenFrames,
|
||||
averageSpacingBetweenFrames
|
||||
@ -98,15 +108,28 @@ function processSingleframe(instances) {
|
||||
}
|
||||
}
|
||||
|
||||
previousIpp = ipp;
|
||||
previousImagePositionPatient = ImagePositionPatient;
|
||||
}
|
||||
}
|
||||
|
||||
return { value: true, missingFrames };
|
||||
}
|
||||
|
||||
// TODO: Is 10% a reasonable tolerance for spacing?
|
||||
const tolerance = 0.1;
|
||||
function _isSameOrientation(iop1, iop2) {
|
||||
if (iop1 === undefined || !iop2 === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
Math.abs(iop1[0] - iop2[0]) < iopTolerance &&
|
||||
Math.abs(iop1[1] - iop2[1]) < iopTolerance &&
|
||||
Math.abs(iop1[2] - iop2[2]) < iopTolerance
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Is 10% a reasonable spacingTolerance for spacing?
|
||||
const spacingTolerance = 0.1;
|
||||
const iopTolerance = 0.01;
|
||||
|
||||
/**
|
||||
* Checks for spacing issues.
|
||||
@ -118,7 +141,7 @@ const tolerance = 0.1;
|
||||
*/
|
||||
function _getSpacingIssue(spacing, averageSpacing) {
|
||||
const equalWithinTolerance =
|
||||
Math.abs(spacing - averageSpacing) < averageSpacing * tolerance;
|
||||
Math.abs(spacing - averageSpacing) < averageSpacing * spacingTolerance;
|
||||
|
||||
if (equalWithinTolerance) {
|
||||
return;
|
||||
@ -131,7 +154,7 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
||||
const errorForEachSpacing =
|
||||
Math.abs(spacing - numberOfSpacings * averageSpacing) / numberOfSpacings;
|
||||
|
||||
if (errorForEachSpacing < tolerance * averageSpacing) {
|
||||
if (errorForEachSpacing < spacingTolerance * averageSpacing) {
|
||||
return {
|
||||
issue: reconstructionIssues.MISSING_FRAMES,
|
||||
missingFrames: numberOfSpacings - 1,
|
||||
@ -141,18 +164,6 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
||||
return { issue: reconstructionIssues.IRREGULAR_SPACING };
|
||||
}
|
||||
|
||||
function _getImagePositionPatient(instance) {
|
||||
const tagValue = instance
|
||||
.getTagValue('x00200032');
|
||||
if (!tagValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
return tagValue
|
||||
.split('\\')
|
||||
.map(element => Number(element));
|
||||
}
|
||||
|
||||
function _getPerpendicularDistance(a, b) {
|
||||
return Math.sqrt(
|
||||
Math.pow(a[0] - b[0], 2) +
|
||||
|
||||
@ -53,10 +53,10 @@ const imagesTypes = [
|
||||
|
||||
/**
|
||||
* Checks whether dicom files with specified SOP Class UID have image data
|
||||
* @param {string} sopClassUid - SOP Class UID to be checked
|
||||
* @param {string} SOPClassUID - SOP Class UID to be checked
|
||||
* @returns {boolean} - true if it has image data
|
||||
*/
|
||||
export const isImage = sopClassUid => {
|
||||
if (!sopClassUid) return false;
|
||||
return imagesTypes.indexOf(sopClassUid) !== -1;
|
||||
export const isImage = SOPClassUID => {
|
||||
if (!SOPClassUID) return false;
|
||||
return imagesTypes.indexOf(SOPClassUID) !== -1;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']);
|
||||
|
||||
export default function isLowPriorityModality(modality) {
|
||||
return LOW_PRIORITY_MODALITIES.includes(modality);
|
||||
export default function isLowPriorityModality(Modality) {
|
||||
return LOW_PRIORITY_MODALITIES.includes(Modality);
|
||||
}
|
||||
|
||||
78
platform/core/src/utils/metadataProvider/fetchOverlayData.js
Normal file
78
platform/core/src/utils/metadataProvider/fetchOverlayData.js
Normal file
@ -0,0 +1,78 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../DICOMWeb';
|
||||
|
||||
export default async function fetchOverlayData(instance, server) {
|
||||
const OverlayDataPromises = [];
|
||||
const OverlayDataTags = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
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`;
|
||||
|
||||
if (instance[OverlayDataTag] && instance[OverlayDataTag].BulkDataURI) {
|
||||
OverlayDataPromises.push(
|
||||
_getOverlayData(instance[OverlayDataTag], server)
|
||||
);
|
||||
OverlayDataTags.push(OverlayDataTag);
|
||||
}
|
||||
}
|
||||
|
||||
if (OverlayDataPromises.length) {
|
||||
Promise.all(OverlayDataPromises).then(results => {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
instance[OverlayDataTags[i]] = results[i];
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function _getOverlayData(tag, server) {
|
||||
const { BulkDataURI } = tag;
|
||||
|
||||
let uri = BulkDataURI;
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
return dicomWeb
|
||||
.retrieveBulkData(options)
|
||||
.then(result => result[0])
|
||||
.then(_unpackOverlay);
|
||||
}
|
||||
|
||||
function _unpackOverlay(arrayBuffer) {
|
||||
const bitArray = new Uint8Array(arrayBuffer);
|
||||
const byteArray = new Uint8Array(8 * bitArray.length);
|
||||
|
||||
for (let byteIndex = 0; byteIndex < byteArray.length; byteIndex++) {
|
||||
const bitIndex = byteIndex % 8;
|
||||
const bitByteIndex = Math.floor(byteIndex / 8);
|
||||
byteArray[byteIndex] =
|
||||
1 * ((bitArray[bitByteIndex] & (1 << bitIndex)) >> bitIndex);
|
||||
}
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
@ -0,0 +1,187 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../DICOMWeb';
|
||||
|
||||
export default async function fetchPaletteColorLookupTableData(
|
||||
instance,
|
||||
server
|
||||
) {
|
||||
const {
|
||||
PaletteColorLookupTableUID,
|
||||
RedPaletteColorLookupTableDescriptor,
|
||||
GreenPaletteColorLookupTableDescriptor,
|
||||
BluePaletteColorLookupTableDescriptor,
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
} = instance;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let entry;
|
||||
if (_paletteColorCache.isValidUID(PaletteColorLookupTableUID)) {
|
||||
entry = _paletteColorCache.get(PaletteColorLookupTableUID);
|
||||
|
||||
if (entry) {
|
||||
return resolve(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// no entry in cache... Fetch remote data.
|
||||
const promises = [
|
||||
_getPaletteColor(
|
||||
server,
|
||||
RedPaletteColorLookupTableData,
|
||||
RedPaletteColorLookupTableDescriptor
|
||||
),
|
||||
_getPaletteColor(
|
||||
server,
|
||||
GreenPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableDescriptor
|
||||
),
|
||||
_getPaletteColor(
|
||||
server,
|
||||
BluePaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableDescriptor
|
||||
),
|
||||
];
|
||||
|
||||
Promise.all(promises).then(
|
||||
([
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
]) => {
|
||||
// when PaletteColorLookupTableUID is present, the entry can be cached...
|
||||
_paletteColorCache.add({
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
PaletteColorLookupTableUID,
|
||||
});
|
||||
|
||||
instance.RedPaletteColorLookupTableData = RedPaletteColorLookupTableData;
|
||||
instance.GreenPaletteColorLookupTableData = GreenPaletteColorLookupTableData;
|
||||
instance.BluePaletteColorLookupTableData = BluePaletteColorLookupTableData;
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple cache schema for retrieved color palettes.
|
||||
*/
|
||||
const _paletteColorCache = {
|
||||
count: 0,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
|
||||
entries: {},
|
||||
isValidUID: function(PaletteColorLookupTableUID) {
|
||||
return (
|
||||
typeof PaletteColorLookupTableUID === 'string' &&
|
||||
PaletteColorLookupTableUID.length > 0
|
||||
);
|
||||
},
|
||||
get: function(PaletteColorLookupTableUID) {
|
||||
let entry = null;
|
||||
if (this.entries.hasOwnProperty(PaletteColorLookupTableUID)) {
|
||||
entry = this.entries[PaletteColorLookupTableUID];
|
||||
// check how the entry is...
|
||||
if (Date.now() - entry.time > this.maxAge) {
|
||||
// entry is too old... remove entry.
|
||||
delete this.entries[PaletteColorLookupTableUID];
|
||||
this.count--;
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
},
|
||||
add: function(entry) {
|
||||
if (this.isValidUID(entry.uid)) {
|
||||
let PaletteColorLookupTableUID = entry.uid;
|
||||
if (this.entries.hasOwnProperty(PaletteColorLookupTableUID) !== true) {
|
||||
this.count++; // increment cache entry count...
|
||||
}
|
||||
entry.time = Date.now();
|
||||
this.entries[PaletteColorLookupTableUID] = entry;
|
||||
// @TODO: Add logic to get rid of old entries and reduce memory usage...
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
||||
const numLutEntries = lutDescriptor[0];
|
||||
const bits = lutDescriptor[2];
|
||||
|
||||
const readUInt16 = (byteArray, position) => {
|
||||
return byteArray[position] + byteArray[position + 1] * 256;
|
||||
};
|
||||
|
||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||
const byteArray = new Uint8Array(arraybuffer);
|
||||
const lut = [];
|
||||
|
||||
if (bits === 16) {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = readUInt16(byteArray, i * 2);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
return lut;
|
||||
};
|
||||
|
||||
if (paletteColorLookupTableData.BulkDataURI) {
|
||||
let uri = paletteColorLookupTableData.BulkDataURI;
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
return dicomWeb
|
||||
.retrieveBulkData(options)
|
||||
.then(result => result[0])
|
||||
.then(arrayBufferToPaletteColorLUT);
|
||||
} else if (paletteColorLookupTableData.InlineBinary) {
|
||||
const inlineBinaryData = atob(paletteColorLookupTableData.InlineBinary);
|
||||
const arraybuffer = _str2ab(inlineBinaryData);
|
||||
|
||||
return new Promise(resolve => {
|
||||
resolve(arrayBufferToPaletteColorLUT(arraybuffer));
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert String to ArrayBuffer
|
||||
*
|
||||
* @param {String} str Input String
|
||||
* @return {ArrayBuffer} Output converted ArrayBuffer
|
||||
*/
|
||||
function _str2ab(str) {
|
||||
const strLen = str.length;
|
||||
const bytes = new Uint8Array(strLen);
|
||||
|
||||
for (let i = 0; i < strLen; i++) {
|
||||
bytes[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes.buffer;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import log from '../../log';
|
||||
|
||||
function getFallbackTagFromInstance(tag, instance) {
|
||||
if (instance[tag]) {
|
||||
return instance[tag];
|
||||
}
|
||||
|
||||
const fallbackTags = fallbackTagsMap[tag];
|
||||
|
||||
if (fallbackTags) {
|
||||
for (let i = 0; i < fallbackTags.length; i++) {
|
||||
const fallbackTag = fallbackTags[i];
|
||||
|
||||
if (instance[fallbackTag]) {
|
||||
log.info(`metadata provider fallback tag ${tag} to ${fallbackTag}`);
|
||||
|
||||
return instance[fallbackTag];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackTagsMap = {
|
||||
PixelSpacing: ['ImagerPixelSpacing'],
|
||||
};
|
||||
|
||||
export { fallbackTagsMap, getFallbackTagFromInstance };
|
||||
@ -1,109 +0,0 @@
|
||||
import { WadoRsMetaDataBuilder } from '../classes/metadata/WadoRsMetaDataBuilder';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import getWADORSImageId from './getWADORSImageId';
|
||||
|
||||
function getRadiopharmaceuticalInfoMetaData(instance) {
|
||||
const radiopharmaceuticalInfo = instance.radiopharmaceuticalInfo;
|
||||
|
||||
if (instance.modality !== 'PT' || !radiopharmaceuticalInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00181072', radiopharmaceuticalInfo.radiopharmaceuticalStartTime)
|
||||
.addTag('00181074', radiopharmaceuticalInfo.radionuclideTotalDose)
|
||||
.addTag('00181075', radiopharmaceuticalInfo.radionuclideHalfLife)
|
||||
.toJSON();
|
||||
}
|
||||
|
||||
const getWadoRsInstanceMetaData = (study, series, instance) => {
|
||||
return new WadoRsMetaDataBuilder()
|
||||
.addTag('00080016', instance.sopClassUid)
|
||||
.addTag('00080018', instance.sopInstanceUid)
|
||||
.addTag('00080021', series.seriesDate)
|
||||
.addTag('00080031', series.seriesTime)
|
||||
.addTag('0008103e', series.seriesDescription)
|
||||
.addTag('00080060', series.modality)
|
||||
.addTag('00101010', study.patientAge)
|
||||
.addTag('00101020', study.patientSize)
|
||||
.addTag('00101030', study.patientWeight)
|
||||
.addTag('0020000d', study.studyInstanceUid)
|
||||
.addTag('00081030', study.studyDescription)
|
||||
.addTag('00100010', study.patientName)
|
||||
.addTag('00100020', study.patientId)
|
||||
.addTag('00080020', study.studyDate)
|
||||
.addTag('00080030', study.studyTime)
|
||||
.addTag('00080050', study.accessionNumber)
|
||||
.addTag('00200013', instance.instanceNumber)
|
||||
.addTag('00180050', instance.sliceThickness)
|
||||
.addTag('0020000e', series.seriesInstanceUid)
|
||||
.addTag('00200011', series.seriesNumber)
|
||||
.addTag('00200032', instance.imagePositionPatient, true)
|
||||
.addTag('00200037', instance.imageOrientationPatient, true)
|
||||
.addTag('00200052', instance.frameOfReferenceUID)
|
||||
.addTag('00201041', instance.sliceLocation)
|
||||
.addTag('00280002', instance.samplesPerPixel)
|
||||
.addTag('00280004', instance.photometricInterpretation)
|
||||
.addTag('00280006', instance.planarConfiguration)
|
||||
.addTag('00280010', instance.rows)
|
||||
.addTag('00280011', instance.columns)
|
||||
.addTag('00280030', instance.pixelSpacing, true)
|
||||
.addTag('00280034', instance.pixelAspectRatio, true)
|
||||
.addTag('00280100', instance.bitsAllocated)
|
||||
.addTag('00280101', instance.bitsStored)
|
||||
.addTag('00280102', instance.highBit)
|
||||
.addTag('00280103', instance.pixelRepresentation)
|
||||
.addTag('00280106', instance.smallestPixelValue)
|
||||
.addTag('00280107', instance.largestPixelValue)
|
||||
.addTag('00281050', instance.windowCenter, true)
|
||||
.addTag('00281051', instance.windowWidth, true)
|
||||
.addTag('00281052', instance.rescaleIntercept)
|
||||
.addTag('00281053', instance.rescaleSlope)
|
||||
.addTag('00281054', instance.rescaleType)
|
||||
.addTag('00281101', instance.redPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281102', instance.greenPaletteColorLookupTableDescriptor)
|
||||
.addTag('00281103', instance.bluePaletteColorLookupTableDescriptor)
|
||||
.addTag('00281201', instance.redPaletteColorLookupTableData)
|
||||
.addTag('00281202', instance.greenPaletteColorLookupTableData)
|
||||
.addTag('00281203', instance.bluePaletteColorLookupTableData)
|
||||
.addTag('00540016', getRadiopharmaceuticalInfoMetaData(instance))
|
||||
.toJSON();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update metadata manager with instances of a specifc series
|
||||
* @param {Object} study A plain study descriptor object
|
||||
* @param {Object} series A Series descriptor object contaning the instances to be added to the manager
|
||||
*/
|
||||
function updateMetaDataManagerForSeries(study, series) {
|
||||
series.instances.forEach(instance => {
|
||||
const metaData = getWadoRsInstanceMetaData(study, series, instance);
|
||||
const numberOfFrames = instance.numberOfFrames || 1;
|
||||
|
||||
// We can share the same metaData with all frames because it doesn't have
|
||||
// any frame specific data, such as frameNumber, pixelData, offset, etc.
|
||||
// WADO-RS frame number is 1-based
|
||||
for (let frameNumber = 0; frameNumber < numberOfFrames; frameNumber++) {
|
||||
const imageId = getWADORSImageId(instance, frameNumber);
|
||||
|
||||
// TODO Make a metadata manager which uses study/series/instance UIDS instead of imageIds
|
||||
cornerstoneWADOImageLoader.wadors.metaDataManager.add(imageId, metaData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metadata manager
|
||||
* @param {Object} study A plain study descriptor object
|
||||
* @param {string} [seriesInstanceUid] The Series Instance UID of the series to be added (Optional)
|
||||
*/
|
||||
export default function updateMetaDataManager(study, seriesInstanceUid) {
|
||||
if (seriesInstanceUid) {
|
||||
const series = study.seriesMap[seriesInstanceUid];
|
||||
updateMetaDataManagerForSeries(study, series);
|
||||
} else {
|
||||
study.seriesList.forEach(series => {
|
||||
updateMetaDataManagerForSeries(study, series);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -10,8 +10,8 @@ import './LayoutChooser.styl';
|
||||
|
||||
class LayoutChooser extends PureComponent {
|
||||
static propTypes = {
|
||||
rows: PropTypes.number.isRequired,
|
||||
columns: PropTypes.number.isRequired,
|
||||
Rows: PropTypes.number.isRequired,
|
||||
Columns: PropTypes.number.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
selectedCell: PropTypes.object,
|
||||
boxSize: PropTypes.number.isRequired,
|
||||
@ -21,8 +21,8 @@ class LayoutChooser extends PureComponent {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
rows: 3,
|
||||
columns: 3,
|
||||
Rows: 3,
|
||||
Columns: 3,
|
||||
visible: true,
|
||||
boxSize: 20,
|
||||
cellBorder: 1,
|
||||
@ -63,9 +63,9 @@ class LayoutChooser extends PureComponent {
|
||||
};
|
||||
highlightCells = currentCell => {
|
||||
let table = [];
|
||||
for (let row = 0; row < this.props.rows; row++) {
|
||||
for (let row = 0; row < this.props.Rows; row++) {
|
||||
let newRow = [];
|
||||
for (let col = 0; col < this.props.columns; col++) {
|
||||
for (let col = 0; col < this.props.Columns; col++) {
|
||||
let cell = { row: row, col: col };
|
||||
if (this.isRange(cell, currentCell)) {
|
||||
cell.className = 'hover';
|
||||
@ -83,11 +83,11 @@ class LayoutChooser extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
let columns = this.props.columns;
|
||||
let Columns = this.props.Columns;
|
||||
const style = {
|
||||
display: this.props.visible ? 'block' : 'none',
|
||||
minWidth:
|
||||
columns * this.props.boxSize + (columns + 5) * this.props.cellBorder,
|
||||
Columns * this.props.boxSize + (Columns + 5) * this.props.cellBorder,
|
||||
};
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -12,10 +12,10 @@ export class QuickSwitch extends Component {
|
||||
side: PropTypes.string,
|
||||
studyListData: PropTypes.array.isRequired,
|
||||
onSeriesSelected: PropTypes.func.isRequired,
|
||||
seriesListData: PropTypes.array,
|
||||
seriesData: PropTypes.array,
|
||||
onStudySelected: PropTypes.func,
|
||||
activeStudyInstanceUid: PropTypes.string,
|
||||
activeDisplaySetInstanceUid: PropTypes.string,
|
||||
activeStudyInstanceUID: PropTypes.string,
|
||||
activeDisplaySetInstanceUID: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@ -24,26 +24,26 @@ export class QuickSwitch extends Component {
|
||||
this.state = {
|
||||
seriesQuickSwitchOpen: false,
|
||||
sideClass: this.props.side || '',
|
||||
activeStudyInstanceUid: this.props.activeStudyInstanceUid,
|
||||
activeDisplaySetInstanceUid: this.props.activeDisplaySetInstanceUid,
|
||||
activeStudyInstanceUID: this.props.activeStudyInstanceUID,
|
||||
activeDisplaySetInstanceUID: this.props.activeDisplaySetInstanceUID,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.activeStudyInstanceUid !== prevProps.activeStudyInstanceUid) {
|
||||
if (props.activeStudyInstanceUID !== prevProps.activeStudyInstanceUID) {
|
||||
this.setState({
|
||||
activeStudyInstanceUid: props.activeStudyInstanceUid,
|
||||
activeStudyInstanceUID: props.activeStudyInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
props.activeDisplaySetInstanceUid !==
|
||||
prevProps.activeDisplaySetInstanceUid
|
||||
props.activeDisplaySetInstanceUID !==
|
||||
prevProps.activeDisplaySetInstanceUID
|
||||
) {
|
||||
this.setState({
|
||||
activeDisplaySetInstanceUid: props.activeDisplaySetInstanceUid,
|
||||
activeDisplaySetInstanceUID: props.activeDisplaySetInstanceUID,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -55,9 +55,7 @@ export class QuickSwitch extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`series-quick-switch clearfix noselect ${
|
||||
this.state.sideClass
|
||||
} ${quickSwitchClass}`}
|
||||
className={`series-quick-switch clearfix noselect ${this.state.sideClass} ${quickSwitchClass}`}
|
||||
onMouseLeave={this.hideSeriesSwitch}
|
||||
>
|
||||
<div className="series-switch" onMouseEnter={this.showSeriesSwitch}>
|
||||
@ -68,8 +66,8 @@ export class QuickSwitch extends Component {
|
||||
<SeriesList
|
||||
seriesItems={this.getSeriesItems()}
|
||||
onClick={this.onSeriesClick}
|
||||
activeDisplaySetInstanceUid={
|
||||
this.state.activeDisplaySetInstanceUid
|
||||
activeDisplaySetInstanceUID={
|
||||
this.state.activeDisplaySetInstanceUID
|
||||
}
|
||||
/>
|
||||
</ScrollableArea>
|
||||
@ -82,7 +80,7 @@ export class QuickSwitch extends Component {
|
||||
<StudiesList
|
||||
studyListData={this.props.studyListData}
|
||||
onClick={this.onStudyClick}
|
||||
activeStudyInstanceUid={this.state.activeStudyInstanceUid}
|
||||
activeStudyInstanceUID={this.state.activeStudyInstanceUID}
|
||||
/>
|
||||
</ScrollableArea>
|
||||
</div>
|
||||
@ -92,29 +90,29 @@ export class QuickSwitch extends Component {
|
||||
}
|
||||
|
||||
getSeriesItems = () => {
|
||||
let seriesListData;
|
||||
let seriesData;
|
||||
|
||||
if (this.props.seriesListData) {
|
||||
seriesListData = this.props.seriesListData;
|
||||
} else if (this.state.activeStudyInstanceUid) {
|
||||
if (this.props.seriesData) {
|
||||
seriesData = this.props.seriesData;
|
||||
} else if (this.state.activeStudyInstanceUID) {
|
||||
const study = this.props.studyListData.find(
|
||||
study => study.studyInstanceUid === this.state.activeStudyInstanceUid
|
||||
study => study.StudyInstanceUID === this.state.activeStudyInstanceUID
|
||||
);
|
||||
|
||||
seriesListData = study.thumbnails;
|
||||
seriesData = study.thumbnails;
|
||||
} else {
|
||||
seriesListData = this.props.studyListData[0].thumbnails;
|
||||
seriesData = this.props.studyListData[0].thumbnails;
|
||||
}
|
||||
|
||||
return seriesListData || [];
|
||||
return seriesData || [];
|
||||
};
|
||||
|
||||
getSmallListItems = () => {
|
||||
const seriesItems = this.getSeriesItems() || [];
|
||||
return seriesItems.map((seriesData, index) => {
|
||||
const active =
|
||||
seriesData.displaySetInstanceUid ===
|
||||
this.state.activeDisplaySetInstanceUid;
|
||||
seriesData.displaySetInstanceUID ===
|
||||
this.state.activeDisplaySetInstanceUID;
|
||||
return (
|
||||
<div key={index} className={`series-item ${active ? 'active' : ''}`} />
|
||||
);
|
||||
@ -126,14 +124,14 @@ export class QuickSwitch extends Component {
|
||||
this.props.onStudySelected(studyDataSelected);
|
||||
}
|
||||
this.setState({
|
||||
activeStudyInstanceUid: studyDataSelected.studyInstanceUid,
|
||||
activeStudyInstanceUID: studyDataSelected.StudyInstanceUID,
|
||||
seriesQuickSwitchOpen: true,
|
||||
});
|
||||
};
|
||||
|
||||
onSeriesClick = seriesDataSelected => {
|
||||
this.setState({
|
||||
activeDisplaySetInstanceUid: seriesDataSelected.displaySetInstanceUid,
|
||||
activeDisplaySetInstanceUID: seriesDataSelected.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
this.props.onSeriesSelected(seriesDataSelected);
|
||||
|
||||
@ -8,7 +8,7 @@ export class SeriesList extends Component {
|
||||
static propTypes = {
|
||||
seriesItems: PropTypes.array.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
activeDisplaySetInstanceUid: PropTypes.string,
|
||||
activeDisplaySetInstanceUID: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -25,12 +25,12 @@ export class SeriesList extends Component {
|
||||
return this.props.seriesItems.map((seriesData, index) => {
|
||||
return (
|
||||
<Thumbnail
|
||||
key={seriesData.displaySetInstanceUid}
|
||||
key={seriesData.displaySetInstanceUID}
|
||||
id={`series_thumb_${index}`}
|
||||
{...seriesData}
|
||||
active={
|
||||
seriesData.displaySetInstanceUid ===
|
||||
this.props.activeDisplaySetInstanceUid
|
||||
seriesData.displaySetInstanceUID ===
|
||||
this.props.activeDisplaySetInstanceUID
|
||||
}
|
||||
onClick={() => this.props.onClick(seriesData)}
|
||||
/>
|
||||
|
||||
@ -12,13 +12,13 @@ export class StudiesItem extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
studyDate,
|
||||
studyDescription,
|
||||
StudyDate,
|
||||
StudyDescription,
|
||||
modalities,
|
||||
studyAvailable,
|
||||
} = this.props.studyData;
|
||||
const activeClass = this.props.active ? ' active' : '';
|
||||
const hasDescriptionAndDate = studyDate && studyDescription;
|
||||
const hasDescriptionAndDate = StudyDate && StudyDescription;
|
||||
return (
|
||||
<div
|
||||
className={`studyBrowseItem${activeClass}`}
|
||||
@ -36,8 +36,8 @@ export class StudiesItem extends Component {
|
||||
<div className="studyText">
|
||||
{hasDescriptionAndDate ? (
|
||||
<React.Fragment>
|
||||
<div className="studyDate">{studyDate}</div>
|
||||
<div className="studyDescription">{studyDescription}</div>
|
||||
<div className="studyDate">{StudyDate}</div>
|
||||
<div className="studyDescription">{StudyDescription}</div>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className="studyAvailability">
|
||||
|
||||
@ -9,7 +9,7 @@ export class StudiesList extends Component {
|
||||
class: PropTypes.string,
|
||||
studyListData: PropTypes.array.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
activeStudyInstanceUid: PropTypes.string,
|
||||
activeStudyInstanceUID: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -27,7 +27,7 @@ export class StudiesList extends Component {
|
||||
key={index}
|
||||
studyData={studyData}
|
||||
active={
|
||||
studyData.studyInstanceUid === this.props.activeStudyInstanceUid
|
||||
studyData.StudyInstanceUID === this.props.activeStudyInstanceUID
|
||||
}
|
||||
onClick={event => this.props.onClick(studyData)}
|
||||
/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user