IDC 2259: add warning UI for series inconsistencies (#2331)

IDC 2259: add warning UI for series inconsistencies

remove VTKMPRToolbarButton component memoization (it is not anymore necessary)
This commit is contained in:
Davide Punzo 2021-03-26 11:32:10 +01:00 committed by GitHub
parent db3b15e537
commit 0456d92462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 327 additions and 185 deletions

View File

@ -1,18 +1,21 @@
import React, { useMemo } from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ToolbarButton } from '@ohif/ui'; import { ToolbarButton } from '@ohif/ui';
import { utils } from '@ohif/core'; import { utils } from '@ohif/core';
import { createSelector } from 'reselect';
const { studyMetadataManager } = utils; const { studyMetadataManager } = utils;
const _isDisplaySetReconstructable = ( let isVisible = true;
displaySetInstanceUID = '',
StudyInstanceUID = '' const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
) => { if (!viewportSpecificData[activeViewportIndex]) {
if (displaySetInstanceUID == '' || StudyInstanceUID == '') {
return false; return false;
} };
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
activeViewportIndex
];
const studies = studyMetadataManager.all(); const studies = studyMetadataManager.all();
@ -24,132 +27,38 @@ const _isDisplaySetReconstructable = (
return false; return false;
} }
const displaySet = study._displaySets.find( const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
set => set.displaySetInstanceUID === displaySetInstanceUID
);
if (!displaySet) { if (!displaySet) {
return false; return false;
} };
// 2D MPR is not currently available for 4D datasets.
// Assuming that slices at different time have the same position, here we just check if
// there are multiple slices for the same ImagePositionPatient and disable MPR.
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
for (let ii = 0; ii < displaySet.numImageFrames; ++ii) {
const image = displaySet.images[ii];
if (!image) continue;
const imageIdControl = image.getImageId();
const instanceMetadataControl = cornerstone.metaData.get(
'instance',
imageIdControl
);
if (
!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined
) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatientControl =
instanceMetadataControl.ImagePositionPatient[0];
let yImagePositionPatientControl =
instanceMetadataControl.ImagePositionPatient[1];
let zImagePositionPatientControl =
instanceMetadataControl.ImagePositionPatient[2];
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj) {
const image = displaySet.images[jj];
if (!image) continue;
const imageId = image.getImageId();
const instanceMetadata = cornerstone.metaData.get('instance', imageId);
if (
!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined
) {
// if ImagePositionPatient is missing, skip the 4D datasets check.
// do not return false, because it could be a 3D dataset.
continue;
}
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
// ImagePositionPatient is float
if (
Math.abs( xImagePositionPatientControl - xImagePositionPatient ) < 1e-6 &&
Math.abs( yImagePositionPatientControl - yImagePositionPatient ) < 1e-6 &&
Math.abs( zImagePositionPatientControl - zImagePositionPatient ) < 1e-6
) {
return false;
}
}
}
return displaySet.isReconstructable; return displaySet.isReconstructable;
}; };
const selectDisplaySetInstanceUID = state => { function VTKMPRToolbarButton({
if ( parentContext,
state.viewports.viewportSpecificData[state.viewports.activeViewportIndex] toolbarClickCallback,
) { button,
return state.viewports.viewportSpecificData[ activeButtons,
state.viewports.activeViewportIndex isActive,
].displaySetInstanceUID; className,
} else { }) {
return '';
}
};
const selectStudyInstanceUID = state => {
if (
state.viewports.viewportSpecificData[state.viewports.activeViewportIndex]
) {
return state.viewports.viewportSpecificData[
state.viewports.activeViewportIndex
].StudyInstanceUID;
} else {
return '';
}
};
const stateSelector = createSelector(
[selectDisplaySetInstanceUID, selectStudyInstanceUID],
(displaySetInstanceUID, StudyInstanceUID) => {
return {
displaySetInstanceUID,
StudyInstanceUID,
};
}
);
function VTKMPRToolbarButton({ toolbarClickCallback, button, isActive }) {
const { id, label, icon } = button; const { id, label, icon } = button;
const { displaySetInstanceUID, StudyInstanceUID } = useSelector( const { viewportSpecificData, activeViewportIndex } = useSelector(state => {
stateSelector const { viewports = {} } = state;
); const { viewportSpecificData, activeViewportIndex } = viewports;
const isVisible = useMemo(() => { return {
return _isDisplaySetReconstructable( viewportSpecificData,
displaySetInstanceUID, activeViewportIndex,
StudyInstanceUID }
); });
}, [displaySetInstanceUID, StudyInstanceUID]);
isVisible = _isDisplaySetReconstructable(
viewportSpecificData,
activeViewportIndex,
);
return ( return (
<React.Fragment> <React.Fragment>

View File

@ -767,6 +767,16 @@ const isMultiFrame = instance => {
return instance.getTagValue('NumberOfFrames') > 1; return instance.getTagValue('NumberOfFrames') > 1;
}; };
/**
* Creates a display set for a series.
* Checks if a series is reconstructable to a 3D volume.
* If reconstructable, the frames are sorted.
*
* @param {SeriesMetadata} series The series metadata object from which the display sets will be created
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} imageSet.
*/
const makeDisplaySet = (series, instances) => { const makeDisplaySet = (series, instances) => {
const instance = instances[0]; const instance = instances[0];
const imageSet = new ImageSet(instances); const imageSet = new ImageSet(instances);
@ -804,18 +814,22 @@ const makeDisplaySet = (series, instances) => {
imageSet.getImage(0).getTagValue('InstanceNumber') imageSet.getImage(0).getTagValue('InstanceNumber')
); );
const isReconstructable = isDisplaySetReconstructable(instances); const displayReconstructableInfo = isDisplaySetReconstructable(instances);
imageSet.isReconstructable = displayReconstructableInfo.value;
imageSet.isReconstructable = isReconstructable.value;
if (shallSort && imageSet.isReconstructable) { if (shallSort && imageSet.isReconstructable) {
imageSet.sortByImagePositionPatient(); imageSet.sortByImagePositionPatient();
} }
if (isReconstructable.missingFrames) { if (displayReconstructableInfo.missingFrames) {
// TODO -> This is currently unused, but may be used for reconstructing // TODO -> This is currently unused, but may be used for reconstructing
// Volumes with gaps later on. // Volumes with gaps later on.
imageSet.missingFrames = isReconstructable.missingFrames; imageSet.missingFrames = displayReconstructableInfo.missingFrames;
}
if (!imageSet.displayReconstructableInfo) {
// It is not reconstrabale Save type of warning
imageSet.warningIssues = displayReconstructableInfo.warningIssues;
} }
return imageSet; return imageSet;

View File

@ -0,0 +1,11 @@
const ReconstructionIssues = {
DATASET_4D: 'datasetis4D',
VARYING_IMAGESDIMENSIONS: 'imagesdimensionsvarying',
VARYING_IMAGESCOMPONENTS: 'imagescomponentsvarying',
VARYING_IMAGESORIENTATION: 'imagesorientationvarying',
MISSING_FRAMES: 'missingframes',
IRREGULAR_SPACING: 'irregularspacing',
MULTIFFRAMES: 'multiframe',
};
export {ReconstructionIssues};

View File

@ -1,7 +1,12 @@
import { ReconstructionIssues } from './../enums.js';
/** /**
* Checks if a series is reconstructable to a 3D volume. * Checks if a series is reconstructable to a 3D volume.
* *
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects. * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} reconstructable value, missingFrames and warningIssues.
*/ */
export default function isDisplaySetReconstructable(instances) { export default function isDisplaySetReconstructable(instances) {
if (!instances.length) { if (!instances.length) {
@ -29,12 +34,28 @@ export default function isDisplaySetReconstructable(instances) {
} }
} }
/**
* Process reconstructable multiframes checks
* TODO: deal with multriframe checks! return false for now as can't reconstruct.
*
* @param {Object} instanceof `OHIFInstanceMetadata` objects. Currently not used.
*
* @returns {Object} reconstructable value and warningIssues.
*/
function processMultiframe(instance) { function processMultiframe(instance) {
//TODO: deal with multriframe checks! return false for now as can't reconstruct. const warningIssues = [ReconstructionIssues.MULTIFRAMES];
return { value: false }; return { value: false, warningIssues };
} }
/**
* Process reconstructable single frame checks
*
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} reconstructable value, missingFrames and warningIssues.
*/
function processSingleframe(instances) { function processSingleframe(instances) {
const n = instances.length;
const firstImage = instances[0].getData().metadata; const firstImage = instances[0].getData().metadata;
const firstImageRows = firstImage.Rows; const firstImageRows = firstImage.Rows;
const firstImageColumns = firstImage.Columns; const firstImageColumns = firstImage.Columns;
@ -42,12 +63,13 @@ function processSingleframe(instances) {
const firstImageOrientationPatient = firstImage.ImageOrientationPatient; const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
const firstImagePositionPatient = firstImage.ImagePositionPatient; const firstImagePositionPatient = firstImage.ImagePositionPatient;
const warningIssues = [];
// Can't reconstruct if we: // Can't reconstruct if we:
// -- Have a different dimensions within a displaySet. // -- Have a different dimensions within a displaySet.
// -- Have a different number of components within a displaySet. // -- Have a different number of components within a displaySet.
// -- Have different orientations within a displaySet. // -- Have different orientations within a displaySet.
for (let i = 1; i < instances.length; i++) { for (let ii = 1; ii < n; ++ii) {
const instance = instances[i].getData().metadata; const instance = instances[ii].getData().metadata;
const { const {
Rows, Rows,
Columns, Columns,
@ -55,13 +77,16 @@ function processSingleframe(instances) {
ImageOrientationPatient, ImageOrientationPatient,
} = instance; } = instance;
if ( if (Rows !== firstImageRows || Columns !== firstImageColumns) {
Rows !== firstImageRows || warningIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
Columns !== firstImageColumns || } else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
SamplesPerPixel !== firstImageSamplesPerPixel || warningIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
!_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient) } else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
) { warningIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
return { value: false }; }
if (warningIssues.length !== 0) {
break;
} }
} }
@ -70,52 +95,101 @@ function processSingleframe(instances) {
// Check if frame spacing is approximately equal within a spacingTolerance. // Check if frame spacing is approximately equal within a spacingTolerance.
// If spacing is on a uniform grid but we are missing frames, // If spacing is on a uniform grid but we are missing frames,
// Allow reconstruction, but pass back the number of missing frames. // Allow reconstruction, but pass back the number of missing frames.
if (instances.length > 2) { if (n > 2) {
const lastIpp = instances[instances.length - 1].getData().metadata const lastIpp = instances[n - 1].getData().metadata
.ImagePositionPatient; .ImagePositionPatient;
// We can't reconstruct if we are missing ImagePositionPatient values // We can't reconstruct if we are missing ImagePositionPatient values
if (!firstImagePositionPatient || !lastIpp) { if (firstImagePositionPatient && lastIpp) {
return { value: false }; const averageSpacingBetweenFrames =
} _getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
(n - 1);
const averageSpacingBetweenFrames = let previousImagePositionPatient = firstImagePositionPatient;
_getPerpendicularDistance(firstImagePositionPatient, lastIpp) /
(instances.length - 1);
let previousImagePositionPatient = firstImagePositionPatient; for (let ii = 1; ii < n; ++ii) {
const instance = instances[ii].getData().metadata;
const { ImagePositionPatient } = instance;
for (let i = 1; i < instances.length; i++) { const spacingBetweenFrames = _getPerpendicularDistance(
const instance = instances[i].getData().metadata; ImagePositionPatient,
const { ImagePositionPatient } = instance; previousImagePositionPatient
);
const spacingIssue = _getSpacingIssue(
spacingBetweenFrames,
averageSpacingBetweenFrames
);
const spacingBetweenFrames = _getPerpendicularDistance( if (spacingIssue) {
ImagePositionPatient, const issue = spacingIssue.issue;
previousImagePositionPatient
);
const spacingIssue = _getSpacingIssue(
spacingBetweenFrames,
averageSpacingBetweenFrames
);
if (spacingIssue) { if (issue === ReconstructionIssues.MISSING_FRAMES) {
const issue = spacingIssue.issue; missingFrames += spacingIssue.missingFrames;
} else if (issue === ReconstructionIssues.IRREGULAR_SPACING) {
if (issue === reconstructionIssues.MISSING_FRAMES) { warningIssues.push(issue);
missingFrames += spacingIssue.missingFrames; break;
} else if (issue === reconstructionIssues.IRREGULAR_SPACING) { }
return { value: false };
} }
}
previousImagePositionPatient = ImagePositionPatient; previousImagePositionPatient = ImagePositionPatient;
}
} }
} }
return { value: true, missingFrames }; // check if dataset is 4D
if (_isDataset4D(instances)) {
warningIssues.push(ReconstructionIssues.DATASET_4D);
}
return { value: warningIssues.length === 0 ? true : false, missingFrames, warningIssues };
} }
function _isSameOrientation(iop1, iop2) { /**
* Check if 4D dataset.
*
* Assuming that slices at different time have the same position, here we just check if
* there are multiple slices for the same ImagePositionPatient and disable MPR.
*
* A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
* However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
*
* @param {Object[]} instances An array of `OHIFInstanceMetadata` objects.
*
* @returns {boolean} reconstructable value.
*/
function _isDataset4D(instances) {
const n = instances.length;
for (let ii = 0; ii < n; ++ii) {
const instanceMetadataControl = instances[ii].getData().metadata;
if (
!instanceMetadataControl ||
instanceMetadataControl === undefined ||
!instanceMetadataControl.ImagePositionPatient ||
instanceMetadataControl.ImagePositionPatient === undefined
) {
continue;
}
for (let jj = ii + 1; jj < n; ++jj) {
const instanceMetadata = instances[jj].getData().metadata;
if (
!instanceMetadata ||
instanceMetadata === undefined ||
!instanceMetadata.ImagePositionPatient ||
instanceMetadata.ImagePositionPatient === undefined
) {
continue;
}
if (_isSameArray(instanceMetadataControl.ImagePositionPatient, instanceMetadata.ImagePositionPatient)) {
return true;
}
}
}
return false;
}
function _isSameArray(iop1, iop2) {
if (iop1 === undefined || !iop2 === undefined) { if (iop1 === undefined || !iop2 === undefined) {
return; return;
} }
@ -156,12 +230,12 @@ function _getSpacingIssue(spacing, averageSpacing) {
if (errorForEachSpacing < spacingTolerance * averageSpacing) { if (errorForEachSpacing < spacingTolerance * averageSpacing) {
return { return {
issue: reconstructionIssues.MISSING_FRAMES, issue: ReconstructionIssues.MISSING_FRAMES,
missingFrames: numberOfSpacings - 1, missingFrames: numberOfSpacings - 1,
}; };
} }
return { issue: reconstructionIssues.IRREGULAR_SPACING }; return { issue: ReconstructionIssues.IRREGULAR_SPACING };
} }
function _getPerpendicularDistance(a, b) { function _getPerpendicularDistance(a, b) {
@ -173,7 +247,3 @@ function _getPerpendicularDistance(a, b) {
} }
const constructableModalities = ['MR', 'CT', 'PT', 'NM']; const constructableModalities = ['MR', 'CT', 'PT', 'NM'];
const reconstructionIssues = {
MISSING_FRAMES: 'missingframes',
IRREGULAR_SPACING: 'irregularspacing',
};

View File

@ -28,6 +28,7 @@ function StudyBrowser(props) {
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
stackPercentComplete, stackPercentComplete,
hasWarnings,
} = thumb; } = thumb;
return ( return (
@ -50,6 +51,7 @@ function StudyBrowser(props) {
numImageFrames={numImageFrames} numImageFrames={numImageFrames}
SeriesDescription={SeriesDescription} SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber} SeriesNumber={SeriesNumber}
hasWarnings={hasWarnings}
stackPercentComplete={stackPercentComplete} stackPercentComplete={stackPercentComplete}
// Events // Events
onClick={onThumbnailClick.bind( onClick={onThumbnailClick.bind(

View File

@ -1,8 +1,11 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd'; import { useDrag } from 'react-dnd';
import ImageThumbnail from './ImageThumbnail'; import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
import './Thumbnail.styl'; import './Thumbnail.styl';
@ -11,7 +14,22 @@ function ThumbnailFooter({
SeriesNumber, SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames, numImageFrames,
hasWarnings
}) { }) {
const [warningList, warningListSet] = useState([]);
useEffect(() => {
let unmounted = false
hasWarnings.then(response => {
if (!unmounted) {
warningListSet(response)
}
})
return () => {
unmounted = true
}
}, [])
const infoOnly = !SeriesDescription; const infoOnly = !SeriesDescription;
const getInfo = (value, icon, className = '') => { const getInfo = (value, icon, className = '') => {
@ -22,28 +40,73 @@ function ThumbnailFooter({
</div> </div>
); );
}; };
const getWarningContent = (warningList) => {
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
const getWarningInfo = (SeriesNumber, warningList) => {
return(
<React.Fragment>
{warningList.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(warningList)}</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
const getSeriesInformation = ( const getSeriesInformation = (
SeriesNumber, SeriesNumber,
InstanceNumber, InstanceNumber,
numImageFrames numImageFrames,
warningList
) => { ) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) { if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return; return;
} }
const seriesInformation =
return (
<div className="series-information"> <div className="series-information">
{getInfo(SeriesNumber, 'S:')} {getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')} {getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')} {getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, warningList)}
</div> </div>
);
return (seriesInformation);
}; };
return ( return (
<div className={classNames('series-details', { 'info-only': infoOnly })}> <div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div> <div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames)} {getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, warningList)}
</div> </div>
); );
} }
@ -60,6 +123,7 @@ function Thumbnail(props) {
numImageFrames, numImageFrames,
SeriesDescription, SeriesDescription,
SeriesNumber, SeriesNumber,
hasWarnings,
stackPercentComplete, stackPercentComplete,
StudyInstanceUID, StudyInstanceUID,
onClick, onClick,
@ -126,13 +190,14 @@ Thumbnail.propTypes = {
stackPercentComplete: PropTypes.number, stackPercentComplete: PropTypes.number,
/** /**
altImageText will be used when no imageId or imageSrc is provided. altImageText will be used when no imageId or imageSrc is provided.
It will be displayed inside the <div>. This is useful when it is difficult It will be displayed inside the <div>. This is useful when it is difficult
to make a preview for a type of DICOM series (e.g. DICOM-SR) to make a preview for a type of DICOM series (e.g. DICOM-SR)
*/ */
altImageText: PropTypes.string, altImageText: PropTypes.string,
SeriesDescription: PropTypes.string, SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hasWarnings: PropTypes.instanceOf(Promise),
numImageFrames: PropTypes.number, numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func, onDoubleClick: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,

View File

@ -78,6 +78,17 @@
height: 11px height: 11px
width: 11px width: 11px
.warning
margin: auto 0;
opacity: 1
color: #e29e4a;
svg
width: 16px;
height: 14px;
pointer-events: inherit;
.value .value
color: var(--text-secondary-color); color: var(--text-secondary-color);
display: inline-block display: inline-block

View File

@ -12,6 +12,7 @@ import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js'; import SidePanel from './../components/SidePanel.js';
import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog'; import ErrorBoundaryDialog from './../components/ErrorBoundaryDialog';
import { extensionManager } from './../App.js'; import { extensionManager } from './../App.js';
import { ReconstructionIssues } from './../../../core/src/enums.js';
// Contexts // Contexts
import WhiteLabelingContext from '../context/WhiteLabelingContext.js'; import WhiteLabelingContext from '../context/WhiteLabelingContext.js';
@ -367,6 +368,62 @@ class Viewer extends Component {
export default withDialog(Viewer); export default withDialog(Viewer);
/**
* Async function to check if there are any inconsistences in the series
* (i.e. reconstructable to a 3D volume). If not reconstructable MPR is disabled.
* The actual computations are done in isDisplaySetReconstructable.
*
* 1) Is series multiframe?
* 2) Do the frames have different dimensions/numer of components/orientations?
* 3) Has the series any missing frames or irregular spacing?
* 4) Is the series 4D?
*
* @param {*object} displaySet
* @returns {[string]} an array of strings containing the warnings
*/
const _checkForSeriesInconsistencesWarnings = async function (displaySet) {
// NOTE: at the moment this function is async even if it does not perfom any heavy calculation.
// We may add or move here some of the computations
// done when creating the displaySet (see makeDisplaySet and isDisplaySetReconstructable).
// the thumbnail footnotes warning react element is already set up to handle a promise.
const warningsList = [];
if (displaySet.warningIssues && displaySet.warningIssues.length !== 0) {
displaySet.warningIssues.forEach(warning => {
switch (warning) {
case ReconstructionIssues.DATASET_4D:
warningsList.push("The dataset is 4D.");
break;
case ReconstructionIssues.VARYING_IMAGESDIMENSIONS:
warningsList.push("The dataset frames have different dimensions (rows, columns).");
break;
case ReconstructionIssues.VARYING_IMAGESCOMPONENTS:
warningsList.push("The dataset frames have different components (Sample per pixel).");
break;
case ReconstructionIssues.VARYING_IMAGESORIENTATION:
warningsList.push("The dataset frames have different orientation.");
break;
case ReconstructionIssues.IRREGULAR_SPACING:
warningsList.push("The dataset frames have different pixel spacing.");
break;
case ReconstructionIssues.MULTIFFRAMES:
warningsList.push("The dataset is a multiframes.");
break;
default:
break;
}
});
warningsList.push('The datasets is not a reconstructable 3D volume. MPR mode is not available.');
}
if (displaySet.missingFrames &&
(!displaySet.warningIssues ||
(displaySet.warningIssues && !displaySet.warningIssues.find(warn => warn === ReconstructionIssues.DATASET_4D)))) {
warningsList.push('The datasets is missing frames: ' + displaySet.missingFrames + '.');
}
return warningsList
}
/** /**
* What types are these? Why do we have "mapping" dropped in here instead of in * What types are these? Why do we have "mapping" dropped in here instead of in
* a mapping layer? * a mapping layer?
@ -406,6 +463,8 @@ const _mapStudiesToThumbnails = function(studies) {
altImageText = displaySet.Modality ? displaySet.Modality : 'UN'; altImageText = displaySet.Modality ? displaySet.Modality : 'UN';
} }
const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet)
return { return {
imageId, imageId,
altImageText, altImageText,
@ -414,6 +473,7 @@ const _mapStudiesToThumbnails = function(studies) {
InstanceNumber, InstanceNumber,
numImageFrames, numImageFrames,
SeriesNumber, SeriesNumber,
hasWarnings,
}; };
}); });