Re IDC #2604: show series (with unsupported modalities) on slides panel and on tag explorer (#2621)

* Re IDC #2604: show series (with unsupported modalities) on slides panel and on tag explorer
This commit is contained in:
Davide Punzo 2021-11-30 15:15:51 +01:00 committed by GitHub
parent 460fdeb534
commit 65505d9bce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 131 deletions

View File

@ -57,9 +57,10 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
});
let metadata;
const isImageStack = activeDisplaySet instanceof ImageSet;
const isImageStack =
activeDisplaySet instanceof ImageSet &&
activeDisplaySet.isModalitySupported === true;
let selectedInstanceValue;
let instanceList;
if (isImageStack) {
@ -91,7 +92,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
setInstanceList(instanceList);
setDisplaySetList(newDisplaySetList);
setIsImageStack(isImageStack);
}, [activeDisplaySetInstanceUID, activeInstance]);
}, [activeDisplaySetInstanceUID, activeInstance, displaySets]);
const selectedDisplaySetValue = displaySetList.find(
ds => ds.value === activeDisplaySetInstanceUID

View File

@ -1,6 +1,4 @@
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
import getImageId from '../utils/getImageId.js';
const noop = () => {};
@ -94,7 +92,11 @@ export class StudyPrefetcher {
}
this.element = element;
this.enabledElement = cornerstone.getEnabledElement(element);
try {
this.enabledElement = cornerstone.getEnabledElement(element);
} catch {
throw new Error('Failed to find the enabled element');
}
this.stopPrefetching();
this.prefetchDisplaySets(displaySetInstanceUID);
@ -114,7 +116,11 @@ export class StudyPrefetcher {
* @param {number} timeout
*/
prefetchDisplaySetsAsync(element, timeout) {
this.enabledElement = cornerstone.getEnabledElement(element);
try {
this.enabledElement = cornerstone.getEnabledElement(element);
} catch {
throw new Error('Failed to find the enabled element');
}
timeout = timeout || this.options.prefetchDisplaySetsTimeout;
clearTimeout(this.prefetchDisplaySetsHandler);
this.prefetchDisplaySetsHandler = setTimeout(() => {

View File

@ -9,7 +9,10 @@ import { SeriesMetadata } from './SeriesMetadata';
import { api } from 'dicomweb-client';
// - createStacks
import { isImage } from '../../utils/isImage';
import { isDisplaySetReconstructable, isSpacingUniform } from '../../utils/isDisplaySetReconstructable';
import {
isDisplaySetReconstructable,
isSpacingUniform,
} from '../../utils/isDisplaySetReconstructable';
import errorHandler from '../../errorHandler';
import isLowPriorityModality from '../../utils/isLowPriorityModality';
import getXHRRetryRequestHook from '../../utils/xhrRetryRequestHook';
@ -159,41 +162,64 @@ class StudyMetadata extends Metadata {
// series into another display set.
const stackableInstances = [];
series.forEachInstance(instance => {
let displaySet;
// All imaging modalities must have a valid value for SOPClassUID (x00080016) or Rows (x00280010)
if (
!isImage(instance.getTagValue('SOPClassUID')) &&
!instance.getTagValue('Rows')
) {
return;
}
let displaySet;
if (isMultiFrame(instance)) {
displaySet = makeDisplaySet(series, [instance]);
// we set an empty display and we add a isModalitySupported variable to
// print a warning that the modality is not supported in the thumbnail.
const displaySet = new ImageSet([]);
const seriesData = series.getData();
displaySet.setAttributes({
sopClassUIDs,
isClip: true,
displaySetInstanceUID: displaySet.uid,
SeriesDate: seriesData.SeriesDate,
SeriesTime: seriesData.SeriesTime,
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)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
sopClassUIDs,
SeriesNumber: instance.getTagValue('SeriesNumber'),
SeriesDescription: instance.getTagValue('SeriesDescription'),
numImageFrames: instance.getTagValue('NumberOfFrames'),
frameRate: instance.getTagValue('FrameTime'),
Modality: instance.getTagValue('Modality'),
isMultiFrame: false,
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
isReconstructable: false,
isModalitySupported: false,
metadata: instance.getData().metadata,
});
displaySets.push(displaySet);
} else {
stackableInstances.push(instance);
if (isMultiFrame(instance)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
sopClassUIDs,
isClip: true,
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)) {
displaySet = makeDisplaySet(series, [instance]);
displaySet.setAttributes({
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 {
stackableInstances.push(instance);
}
}
});
@ -227,7 +253,6 @@ class StudyMetadata extends Metadata {
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
}
/**
* Returns the source display set of the derivated display set.
* @param {object} derivatedDisplaySet
@ -242,40 +267,45 @@ class StudyMetadata extends Metadata {
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
ds =>
ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
);
const { metadata } = derivatedDisplaySet;
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
(metadata, otherDisplaySets);
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
metadata,
otherDisplaySets
);
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
let noReferencedSeriesAvailable =
!referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
(metadata);
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
metadata
);
}
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
noReferencedSeriesAvailable =
!referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
(metadata, otherDisplaySets);
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
metadata,
otherDisplaySets
);
}
const referencedSeriesAvailable = referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0;
const referencedSeriesAvailable =
referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0;
if (referencedSeriesAvailable) {
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
;
return referencedDisplaySet;
}
};
}
/**
* Returns a list of derived datasets in the study, filtered by the given filter.
@ -303,7 +333,10 @@ class StudyMetadata extends Metadata {
if (referencedSeriesInstanceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => {
return StudyMetadata.getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID;
return (
StudyMetadata.getReferencedDisplaySet(displaySet, [this])
.SeriesInstanceUID === referencedSeriesInstanceUID
);
}
);
}
@ -311,8 +344,7 @@ class StudyMetadata extends Metadata {
if (referencedFrameOfReferenceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet =>
displaySet.FrameOfReferenceUID ===
referencedFrameOfReferenceUID
displaySet.FrameOfReferenceUID === referencedFrameOfReferenceUID
);
}
@ -497,8 +529,10 @@ class StudyMetadata extends Metadata {
}
}
if (this._displaySets.some(ds =>
ds.displaySetInstanceUID === displaySet.displaySetInstanceUID)
if (
this._displaySets.some(
ds => ds.displaySetInstanceUID === displaySet.displaySetInstanceUID
)
) {
return;
}
@ -867,8 +901,9 @@ const makeDisplaySet = (series, instances) => {
imageSet.sortByImagePositionPatient();
// check if the spacing is uniform and update isReconstructable
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find
(issue => issue === ReconstructionIssues.DATASET_4D);
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find(
issue => issue === ReconstructionIssues.DATASET_4D
);
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
imageSet.isReconstructable = displaySpacingInfo.isUniform;
@ -881,11 +916,15 @@ const makeDisplaySet = (series, instances) => {
if (!imageSet.displayReconstructableInfo) {
// It is not reconstrabale Save type of warning
imageSet.reconstructionIssues = displaySpacingInfo ?
displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) :
displayReconstructableInfo.reconstructionIssues;
imageSet.reconstructionIssues = displaySpacingInfo
? displayReconstructableInfo.reconstructionIssues.concat(
displaySpacingInfo.reconstructionIssues
)
: displayReconstructableInfo.reconstructionIssues;
}
imageSet.isModalitySupported = true;
return imageSet;
};
@ -963,38 +1002,36 @@ function _getDisplaySetFromSopClassModule(
}
/**
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedSeriesSequence.
* @param {object} derivatedDisplaySet.metadata
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence (
metadata,
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedSeriesSequence.
* @param {object} derivatedDisplaySet.metadata
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence(
metadata
) {
if (!metadata.ReferencedSeriesSequence) {
return;
}
let referencedSeriesInstanceUIDs;
const ReferencedSeriesSequence = _toArray(
metadata.ReferencedSeriesSequence
);
const ReferencedSeriesSequence = _toArray(metadata.ReferencedSeriesSequence);
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
return referencedSeriesInstanceUIDs;
};
}
/**
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence(
metadata,
displaySets
) {
@ -1015,22 +1052,25 @@ function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
ReferencedSOPInstanceUID
);
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
if (
referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0
) {
break;
}
}
return referencedSeriesInstanceUIDs;
};
}
/**
* Returns the referenced series instance UIDs by searching the information in the
* SourceImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
* Returns the referenced series instance UIDs by searching the information in the
* SourceImageSequence.
* @param {object} derivatedDisplaySet.metadata
* @param {array[object]} displaysets
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSourceImageSequence(
metadata,
displaySets
) {
@ -1040,9 +1080,7 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
SourceImageSequence = metadata.SourceImageSequence;
} else {
const { PerFrameFunctionalGroupsSequence } = metadata;
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
const firstFunctionalGroups = _toArray(PerFrameFunctionalGroupsSequence)[0];
if (firstFunctionalGroups) {
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
@ -1062,22 +1100,25 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
displaySets,
ReferencedSOPInstanceUID
);
if (referencedSeriesInstanceUIDs && referencedSeriesInstanceUIDs.length !== 0) {
if (
referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0
) {
break;
}
}
return referencedSeriesInstanceUIDs;
};
}
/**
* Returns the referenced series instance UIDs by searching the information in the
* SOPInstanceUID of the displaySets.
* @param {array[object]} displaysets
* @param {string} SOPInstanceUID
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
* Returns the referenced series instance UIDs by searching the information in the
* SOPInstanceUID of the displaySets.
* @param {array[object]} displaysets
* @param {string} SOPInstanceUID
* @return {array[string]} referenced series instance UIDs.
*/
function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
displaySets,
SOPInstanceUID
) {
@ -1098,10 +1139,10 @@ function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
}
}
}
};
}
function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
}
export {StudyMetadata};
export { StudyMetadata };

View File

@ -1,6 +1,5 @@
import { ReconstructionIssues } from './../enums.js';
/**
* Checks if a series is reconstructable to a 3D volume.
*
@ -78,7 +77,9 @@ function processSingleframe(instances) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
} else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
} else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
} else if (
!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)
) {
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
}
@ -92,7 +93,10 @@ function processSingleframe(instances) {
reconstructionIssues.push(ReconstructionIssues.DATASET_4D);
}
return { value: reconstructionIssues.length === 0 ? true : false, reconstructionIssues };
return {
value: reconstructionIssues.length === 0 ? true : false,
reconstructionIssues,
};
}
/**
@ -116,14 +120,12 @@ function isSpacingUniform(instances, datasetIs4D) {
// If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames.
if (n > 2) {
const lastIpp = instances[n - 1].getData().metadata
.ImagePositionPatient;
const lastIpp = instances[n - 1].getData().metadata.ImagePositionPatient;
// We can't reconstruct if we are missing ImagePositionPatient values
if (firstImagePositionPatient && lastIpp) {
const averageSpacingBetweenFrames =
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
(n - 1);
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (n - 1);
let previousImagePositionPatient = firstImagePositionPatient;
@ -136,7 +138,7 @@ function isSpacingUniform(instances, datasetIs4D) {
previousImagePositionPatient
);
if (datasetIs4D && spacingBetweenFrames < 1.e-3) {
if (datasetIs4D && spacingBetweenFrames < 1e-3) {
// the dataset is 4D, if the distance is zero, means that we are
// checking the 4th dimension. Do not return, since we want still to
// check the 3rd dimension spacing.
@ -164,10 +166,13 @@ function isSpacingUniform(instances, datasetIs4D) {
}
}
return { isUniform: reconstructionIssues.length === 0 ? true : false, missingFrames, reconstructionIssues };
return {
isUniform: reconstructionIssues.length === 0 ? true : false,
missingFrames,
reconstructionIssues,
};
}
/**
* Check if 4D dataset.
*
@ -181,7 +186,7 @@ function isSpacingUniform(instances, datasetIs4D) {
*
* @returns {boolean} dataset4D value.
*/
function _isDataset4D(instances) {
function _isDataset4D(instances) {
const n = instances.length;
for (let ii = 0; ii < n; ++ii) {
const instanceMetadataControl = instances[ii].getData().metadata;
@ -204,7 +209,12 @@ function isSpacingUniform(instances, datasetIs4D) {
continue;
}
if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
if (
_isSameArray(
instanceMetadataControl.ImagePositionPatient,
instanceMetadata.ImagePositionPatient
)
) {
return true;
}
}
@ -265,11 +275,11 @@ function _getSpacingIssue(spacing, averageSpacing) {
function _getPerpendicularDistance(a, b) {
return Math.sqrt(
Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
);
}
const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
export {isDisplaySetReconstructable, isSpacingUniform};
export { isDisplaySetReconstructable, isSpacingUniform };

View File

@ -163,11 +163,6 @@ function Thumbnail(props) {
displaySetInstanceUID,
imageId,
imageSrc,
numImageFrames,
SeriesDescription,
SeriesNumber,
hasWarnings,
hasDerivedDisplaySets,
StudyInstanceUID,
onClick,
onDoubleClick,

View File

@ -14,13 +14,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
displaySetInstanceUID
);
const { LoggerService, UINotificationService } = servicesManager.services;
if (displaySet.isDerived) {
const { Modality } = displaySet;
if (Modality === 'SEG' && servicesManager) {
const {
LoggerService,
UINotificationService,
} = servicesManager.services;
const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message });
UINotificationService.show({
@ -55,14 +53,42 @@ const mapDispatchToProps = (dispatch, ownProps) => {
}
if (!displaySet) {
throw new Error(
const error = new Error(
`Referenced series for ${Modality} dataset not present.`
);
const message = `Referenced series for ${Modality} dataset not present.`;
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
}
if (!displaySet) {
throw new Error('Source data not present');
}
if (!displaySet) {
const error = new Error('Source data not present');
const message = 'Source data not present';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
if (displaySet.isModalitySupported === false) {
const error = new Error('Modality not supported');
const message = 'Modality not supported';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
dispatch(setActiveViewportSpecificData(displaySet));

View File

@ -540,6 +540,10 @@ const _checkForSeriesInconsistencesWarnings = async function(
'The datasets is missing frames: ' + displaySet.missingFrames + '.'
);
}
if (displaySet.isModalitySupported === false) {
inconsistencyWarnings.push('The datasets modality is not supported.');
}
} else {
const segMetadata = displaySet.metadata;
if (!segMetadata) {

View File

@ -139,10 +139,11 @@ class ViewerMain extends Component {
displaySetInstanceUID
);
const { LoggerService, UINotificationService } = servicesManager.services;
if (displaySet.isDerived) {
const { Modality } = displaySet;
if (Modality === 'SEG' && servicesManager) {
const {LoggerService, UINotificationService} = servicesManager.services;
const onDisplaySetLoadFailureHandler = error => {
LoggerService.error({ error, message: error.message });
UINotificationService.show({
@ -153,24 +154,41 @@ class ViewerMain extends Component {
});
};
const {referencedDisplaySet} = displaySet.getSourceDisplaySet(
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(
this.props.studies,
true,
onDisplaySetLoadFailureHandler
);
displaySet = referencedDisplaySet;
} else {
displaySet = displaySet.getSourceDisplaySet(this.props.studies);
}
if (!displaySet) {
throw new Error(
`Referenced series for ${Modality} dataset not present.`
);
const error = new Error('Source data not present');
const message = 'Source data not present';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
}
if (displaySet.isModalitySupported === false) {
const error = new Error('Modality not supported');
const message = 'Modality not supported';
LoggerService.error({ error, message });
UINotificationService.show({
autoClose: false,
title: 'Fail to load series',
message,
type: 'error',
});
}
this.props.setViewportSpecificData(viewportIndex, displaySet);
};