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:
James PettsandDanny authored and GitHub committed 2020-03-09 19:03:23 +00:00
1 parent 8833731ad3
commit 1d85f7a74e
142 files changed
+5322 -8570

No files matched your search

+2 -2
View File
@@ -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",
+67 -75
View File
@@ -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,
+2 -2
View File
@@ -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();
+2 -5
View File
@@ -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 {
+33 -37
View File
@@ -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;