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

+5 -5
View File
@@ -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,
};
},
+2 -3
View File
@@ -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 -26
View File
@@ -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);
});
});
+1 -1
View File
@@ -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 });
+2 -2
View File
@@ -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 => {
+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;