Merge branch 'master' into feat/idc-2374

This commit is contained in:
Davide Punzo 2021-06-03 09:25:50 +02:00 committed by GitHub
commit 582b713b84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 815 additions and 311 deletions

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.8...@ohif/extension-cornerstone@2.10.9) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [2.10.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.7...@ohif/extension-cornerstone@2.10.8) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.10.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.10.6...@ohif/extension-cornerstone@2.10.7) (2021-04-15)
**Note:** Version bump only for package @ohif/extension-cornerstone

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "2.10.7",
"version": "2.10.9",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@ -36,7 +36,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.18.4",
"dcmjs": "0.18.8",
"dicom-parser": "^1.8.3",
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import OHIFCornerstoneViewportOverlay from './components/OHIFCornerstoneViewportOverlay'
import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
@ -194,6 +195,7 @@ class OHIFCornerstoneViewport extends Component {
return null;
}
const { viewportIndex } = this.props;
const { inconsistencyWarnings } = this.props.viewportData.displaySet;
const {
imageIds,
currentImageIdIndex,
@ -229,6 +231,10 @@ class OHIFCornerstoneViewport extends Component {
}
};
const warningsOverlay = props => {
return <OHIFCornerstoneViewportOverlay {...props} inconsistencyWarnings={inconsistencyWarnings} />
};
return (
<>
<ConnectedCornerstoneViewport
@ -237,6 +243,7 @@ class OHIFCornerstoneViewport extends Component {
imageIdIndex={currentImageIdIndex}
onNewImageDebounced={newImageHandler}
onNewImageDebounceTime={300}
viewportOverlayComponent={warningsOverlay}
// ~~ Connected (From REDUX)
// frameRate={frameRate}
// isPlaying={false}

View File

@ -0,0 +1,69 @@
.imageViewerViewport.empty ~ .OHIFCornerstoneViewportOverlay {
display: none;
}
.OHIFCornerstoneViewportOverlay {
color: #9ccef9;
}
.OHIFCornerstoneViewportOverlay .overlay-element {
position: absolute;
font-weight: 400;
text-shadow: 1px 1px #000;
pointer-events: none;
}
.OHIFCornerstoneViewportOverlay .top-left {
top: 20px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .top-center {
top: 20px;
padding-top: 20px;
width: 100%;
text-align: center;
}
.OHIFCornerstoneViewportOverlay .top-right {
top: 20px;
right: 20px;
text-align: right;
}
.OHIFCornerstoneViewportOverlay .bottom-left {
bottom: 20px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .bottom-left2 {
bottom: 140px;
left: 20px;
}
.OHIFCornerstoneViewportOverlay .bottom-right {
bottom: 20px;
right: 20px;
text-align: right;
}
.OHIFCornerstoneViewportOverlay.controlsVisible .topright,
.OHIFCornerstoneViewportOverlay.controlsVisible .bottomright {
right: calc(20px + 19px);
}
.OHIFCornerstoneViewportOverlay .warning {
position: absolute;
font-weight: 400;
text-shadow: 1px 1px #000;
pointer-events: inherit;
}
.OHIFCornerstoneViewportOverlay .warning svg {
opacity: 1;
color: #e29e4a;
fill: #e29e4a;
stroke: #e29e4a;
width: 32px;
height: 28px;
}
.OHIFCornerstoneViewportOverlay svg {
color: #9ccef9;
fill: #9ccef9;
stroke: #9ccef9;
background-color: transparent;
margin: 2px;
width: 18px;
height: 18px;
}

View File

@ -0,0 +1,167 @@
import { PureComponent } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import './OHIFCornerstoneViewportOverlay.css';
import {
isValidNumber,
formatNumberPrecision,
formatDICOMDate,
formatDICOMTime,
formatPN,
getCompression
} from '../utils/formatStudy';
import classNames from 'classnames';
import { Icon } from '@ohif/ui/src/elements/Icon';
import { Tooltip } from '@ohif/ui/src/components/tooltip';
import { OverlayTrigger } from '@ohif/ui/src/components/overlayTrigger';
class OHIFCornerstoneViewportOverlay extends PureComponent {
static propTypes = {
scale: PropTypes.number.isRequired,
windowWidth: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired,
]),
windowCenter: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired,
]),
imageId: PropTypes.string.isRequired,
imageIndex: PropTypes.number.isRequired,
stackSize: PropTypes.number.isRequired,
inconsistencyWarnings: PropTypes.array.isRequired
};
render() {
const { imageId, scale, windowWidth, windowCenter, inconsistencyWarnings } = this.props;
if (!imageId) {
return null;
}
const zoomPercentage = formatNumberPrecision(scale * 100, 0);
const seriesMetadata =
cornerstone.metaData.get('generalSeriesModule', imageId) || {};
const imagePlaneModule =
cornerstone.metaData.get('imagePlaneModule', imageId) || {};
const { rows, columns, sliceThickness, sliceLocation } = imagePlaneModule;
const { seriesNumber, seriesDescription } = seriesMetadata;
const generalStudyModule =
cornerstone.metaData.get('generalStudyModule', imageId) || {};
const { studyDate, studyTime, studyDescription } = generalStudyModule;
const patientModule =
cornerstone.metaData.get('patientModule', imageId) || {};
const { patientId, patientName } = patientModule;
const generalImageModule =
cornerstone.metaData.get('generalImageModule', imageId) || {};
const { instanceNumber } = generalImageModule;
const cineModule = cornerstone.metaData.get('cineModule', imageId) || {};
const { frameTime } = cineModule;
const frameRate = formatNumberPrecision(1000 / frameTime, 1);
const compression = getCompression(imageId);
const wwwc = `W: ${
windowWidth.toFixed ? windowWidth.toFixed(0) : windowWidth
} L: ${windowWidth.toFixed ? windowCenter.toFixed(0) : windowCenter}`;
const imageDimensions = `${columns} x ${rows}`;
const { imageIndex, stackSize } = this.props;
const inconsistencyWarningsOn = inconsistencyWarnings && inconsistencyWarnings.length !== 0 ? true : false;
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, inconsistencyWarnings) => {
return(
<React.Fragment>
{inconsistencyWarnings.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(inconsistencyWarnings)}</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
)}
</React.Fragment>
);
};
const normal = (
<React.Fragment>
<div className="top-left overlay-element">
<div>{formatPN(patientName)}</div>
<div>{patientId}</div>
</div>
<div className="top-right overlay-element">
<div>{studyDescription}</div>
<div>
{formatDICOMDate(studyDate)} {formatDICOMTime(studyTime)}
</div>
</div>
<div className="bottom-right overlay-element">
<div>Zoom: {zoomPercentage}%</div>
<div>{wwwc}</div>
<div className="compressionIndicator">{compression}</div>
</div>
<div className="bottom-left2 warning">
<div>{inconsistencyWarningsOn ? getWarningInfo(seriesNumber, inconsistencyWarnings) : ''}</div>
</div>
<div className="bottom-left overlay-element">
<div>{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}</div>
<div>
{stackSize > 1
? `Img: ${instanceNumber} ${imageIndex}/${stackSize}`
: ''}
</div>
<div>
{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}
<div>{imageDimensions}</div>
<div>
{isValidNumber(sliceLocation)
? `Loc: ${formatNumberPrecision(sliceLocation, 2)} mm `
: ''}
{sliceThickness
? `Thick: ${formatNumberPrecision(sliceThickness, 2)} mm`
: ''}
</div>
<div>{seriesDescription}</div>
</div>
</div>
</React.Fragment>
);
return <div className="OHIFCornerstoneViewportOverlay">{normal}</div>;
}
}
export default OHIFCornerstoneViewportOverlay;

View File

@ -0,0 +1,109 @@
import moment from 'moment';
/**
* Checks if value is valid.
*
* @param {number} value
* @returns {boolean} is valid.
*/
function isValidNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Formats number precision.
*
* @param {number} number
* @param {number} precision
* @returns {number} formatted number.
*/
function formatNumberPrecision(number, precision) {
if (number !== null) {
return parseFloat(number).toFixed(precision);
}
}
/**
* Formats DICOM date.
*
* @param {string} date
* @param {string} strFormat
* @returns {string} formatted date.
*/
function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
return moment(date, 'YYYYMMDD').format(strFormat);
}
/**
* DICOM Time is stored as HHmmss.SSS, where:
* HH 24 hour time:
* m mm 0..59 Minutes
* s ss 0..59 Seconds
* S SS SSS 0..999 Fractional seconds
*
* Goal: '24:12:12'
*
* @param {*} time
* @param {string} strFormat
* @returns {string} formatted name.
*/
function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
return moment(time, 'HH:mm:ss').format(strFormat);
}
/**
* Formats a patient name for display purposes
*
* @param {string} name
* @returns {string} formatted name.
*/
function formatPN(name) {
if (!name) {
return;
}
// Convert the first ^ to a ', '. String.replace() only affects
// the first appearance of the character.
const commaBetweenFirstAndLast = name.replace('^', ', ');
// Replace any remaining '^' characters with spaces
const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' ');
// Trim any extraneous whitespace
return cleaned.trim();
}
/**
* Gets compression type
*
* @param {number} imageId
* @returns {string} comrpession type.
*/
function getCompression(imageId) {
const generalImageModule =
cornerstone.metaData.get('generalImageModule', imageId) || {};
const {
lossyImageCompression,
lossyImageCompressionRatio,
lossyImageCompressionMethod,
} = generalImageModule;
if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
const compressionRatio = formatNumberPrecision(
lossyImageCompressionRatio,
2
);
return compressionMethod + compressionRatio + ' : 1';
}
return 'Lossless / Uncompressed';
}
export { isValidNumber,
formatNumberPrecision,
formatDICOMDate,
formatDICOMTime,
formatPN,
getCompression
};

View File

@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.9...@ohif/extension-dicom-html@1.3.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [1.3.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.3.8...@ohif/extension-dicom-html@1.3.9) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-html

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "1.3.9",
"version": "1.3.10",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@ -29,7 +29,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"

View File

@ -168,6 +168,16 @@ const getContentSequence = (data, level = 1) => {
Object.keys(data).forEach(key => {
const value = data[key];
if (key === '_meta') {
const HeaderDynamicLevel = `h3`;
root.push(<hr />);
root.push(
<HeaderDynamicLevel key="Metadata">
DICOM File Meta Information
</HeaderDynamicLevel>
);
}
let content;
if (value instanceof Object) {
content = getContentSequence(value, level + 1);
@ -215,6 +225,7 @@ class DicomHtmlViewport extends Component {
const dicomData = DicomMessage.readFile(arrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
dataset._meta = DicomMetaDictionary.naturalizeDataset(dataset._meta);
const mainData = getMainData(dataset);
const contentSequence = getContentSequence(dataset);

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.12...@ohif/extension-dicom-rt@0.6.13) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.6.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.11...@ohif/extension-dicom-rt@0.6.12) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.10...@ohif/extension-dicom-rt@0.6.11) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-rt

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-rt",
"version": "0.6.11",
"version": "0.6.13",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF",
"license": "MIT",
@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"gl-matrix": "^3.3.0",
"prop-types": "^15.6.2",
"react": "^16.8.6",

View File

@ -71,8 +71,8 @@ const OHIFDicomRTStructSopClassHandler = {
}
}
rtStructDisplaySet.getSourceDisplaySet = function (studies) {
return getSourceDisplaySet(studies, rtStructDisplaySet);
rtStructDisplaySet.getSourceDisplaySet = function (studies, activateLabelMap = true) {
return getSourceDisplaySet(studies, rtStructDisplaySet, activateLabelMap);
};
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {

View File

@ -1,38 +1,14 @@
export default function getSourceDisplaySet(studies, rtStructDisplaySet) {
const referencedDisplaySet = _getReferencedDisplaySet(
import { metadata } from '@ohif/core';
export default function getSourceDisplaySet(studies, rtStructDisplaySet, activateLabelMap = true) {
const referencedDisplaySet = metadata.StudyMetadata.getReferencedDisplaySet(
rtStructDisplaySet,
studies
);
rtStructDisplaySet.load(referencedDisplaySet, studies);
if (activateLabelMap) {
rtStructDisplaySet.load(referencedDisplaySet, studies);
}
return referencedDisplaySet;
}
const _getReferencedDisplaySet = (rtStructDisplaySet, studies) => {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== rtStructDisplaySet.displaySetInstanceUID
);
const ReferencedSeriesSequence = Array.isArray(
rtStructDisplaySet.metadata.ReferencedSeriesSequence
)
? rtStructDisplaySet.metadata.ReferencedSeriesSequence
: [rtStructDisplaySet.metadata.ReferencedSeriesSequence];
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
);
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
return referencedDisplaySet;
};

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.9...@ohif/extension-dicom-segmentation@0.6.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.6.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.8...@ohif/extension-dicom-segmentation@0.6.9) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
## [0.6.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.6.7...@ohif/extension-dicom-segmentation@0.6.8) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-segmentation",
"version": "0.6.8",
"version": "0.6.10",
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF",
"license": "MIT",
@ -31,7 +31,7 @@
"@ohif/core": "^0.50.0",
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"prop-types": "^15.6.2",
"react": "^16.8.6",
"react-dom": "^16.8.6"

View File

@ -1,8 +1,8 @@
import setActiveLabelmap from './utils/setActiveLabelMap';
import { getReferencedDisplaySet } from '../../../platform/core/src/classes/metadata/StudyMetadata.js';
import { metadata } from '@ohif/core';
export default function getSourceDisplaySet(studies, segDisplaySet, activateLabelMap = true, onDisplaySetLoadFailureHandler) {
const referencedDisplaySet = getReferencedDisplaySet(segDisplaySet, studies);
const referencedDisplaySet = metadata.StudyMetadata.getReferencedDisplaySet(segDisplaySet, studies);
let activatedLabelmapPromise;
if (activateLabelMap) {

View File

@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.9...@ohif/extension-dicom-tag-browser@0.2.10) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [0.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.2.8...@ohif/extension-dicom-tag-browser@0.2.9) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-tag-browser",
"version": "0.2.9",
"version": "0.2.10",
"description": "OHIF extension for checking DICOM headers.",
"author": "OHIF",
"license": "MIT",
@ -28,7 +28,7 @@
},
"peerDependencies": {
"@ohif/core": "^2.6.0",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"react": "^16.8.6"
},
"dependencies": {

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.12...@ohif/extension-vtk@1.11.13) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [1.11.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.11...@ohif/extension-vtk@1.11.12) (2021-05-17)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.11.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.11.10...@ohif/extension-vtk@1.11.11) (2021-04-22)
**Note:** Version bump only for package @ohif/extension-vtk

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "1.11.11",
"version": "1.11.13",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@ -35,7 +35,7 @@
"cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"dicom-parser": "^1.8.3",
"i18next": "^17.0.3",
"i18next-browser-languagedetector": "^3.0.1",
@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2"
},
"devDependencies": {
"@ohif/core": "^2.13.10",
"@ohif/ui": "^1.9.1",
"@ohif/core": "^2.13.12",
"@ohif/ui": "^1.9.2",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3",

View File

@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.13.12](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.11...@ohif/core@2.13.12) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [2.13.11](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.10...@ohif/core@2.13.11) (2021-05-17)
**Note:** Version bump only for package @ohif/core
## [2.13.10](https://github.com/OHIF/Viewers/compare/@ohif/core@2.13.9...@ohif/core@2.13.10) (2021-04-22)
**Note:** Version bump only for package @ohif/core

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "2.13.10",
"version": "2.13.12",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@ -39,7 +39,7 @@
"dependencies": {
"@babel/runtime": "^7.5.5",
"ajv": "^6.10.0",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",

View File

@ -227,6 +227,56 @@ class StudyMetadata extends Metadata {
displaySets.map(displaySet => this._derivedDisplaySets.push(displaySet));
}
/**
* Returns the source display set of the derivated display set.
* @param {object} derivatedDisplaySet
* @param {array[StudyMetadata]} studies
* @return {object} source display set.
*/
static getReferencedDisplaySet(derivatedDisplaySet, studies) {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
);
const { metadata } = derivatedDisplaySet;
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
(metadata, otherDisplaySets);
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
(metadata);
}
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
(metadata, otherDisplaySets);
}
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.
* @param {object} filter An object containing search filters
@ -253,7 +303,7 @@ class StudyMetadata extends Metadata {
if (referencedSeriesInstanceUID) {
filteredDerivedDisplaySets = filteredDerivedDisplaySets.filter(
displaySet => {
return getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID;
return StudyMetadata.getReferencedDisplaySet(displaySet, [this]).SeriesInstanceUID === referencedSeriesInstanceUID;
}
);
}
@ -811,7 +861,7 @@ const makeDisplaySet = (series, instances) => {
imageSet.sortByImagePositionPatient();
// check if the spacing is uniform and update isReconstructable
const datasetIs4D = displayReconstructableInfo.warningIssues.find
const datasetIs4D = displayReconstructableInfo.reconstructionIssues.find
(issue => issue === ReconstructionIssues.DATASET_4D);
displaySpacingInfo = isSpacingUniform(imageSet.images, datasetIs4D);
imageSet.isReconstructable = displaySpacingInfo.isUniform;
@ -825,9 +875,9 @@ const makeDisplaySet = (series, instances) => {
if (!imageSet.displayReconstructableInfo) {
// It is not reconstrabale Save type of warning
imageSet.warningIssues = displaySpacingInfo ?
displayReconstructableInfo.warningIssues.concat(displaySpacingInfo.warningIssues) :
displayReconstructableInfo.warningIssues;
imageSet.reconstructionIssues = displaySpacingInfo ?
displayReconstructableInfo.reconstructionIssues.concat(displaySpacingInfo.reconstructionIssues) :
displayReconstructableInfo.reconstructionIssues;
}
return imageSet;
@ -906,55 +956,6 @@ function _getDisplaySetFromSopClassModule(
return displaySet;
}
/**
* Returns the source display set of the derivated display set.
* @param {object} derivatedDisplaySet
* @param {array[StudyMetadata]} studies
* @return {object} source display set.
*/
function getReferencedDisplaySet(derivatedDisplaySet, studies) {
let allDisplaySets = [];
studies.forEach(study => {
allDisplaySets = allDisplaySets.concat(study.displaySets);
});
const otherDisplaySets = allDisplaySets.filter(
ds => ds.displaySetInstanceUID !== derivatedDisplaySet.displaySetInstanceUID
);
const { metadata } = derivatedDisplaySet;
let referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSourceImageSequence
(metadata, otherDisplaySets);
let noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedSeriesSequence
(metadata);
}
noReferencedSeriesAvailable = !referencedSeriesInstanceUIDs ||
referencedSeriesInstanceUIDs.length === 0;
if (noReferencedSeriesAvailable) {
referencedSeriesInstanceUIDs =
_findReferencedSeriesInstanceUIDsFromReferencedImageSequence
(metadata, otherDisplaySets);
}
const referencedSeriesAvailable = referencedSeriesInstanceUIDs &&
referencedSeriesInstanceUIDs.length !== 0;
if (referencedSeriesAvailable) {
const referencedDisplaySet = otherDisplaySets.find(ds =>
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
);
;
return referencedDisplaySet;
}
};
/**
* Returns the referenced series instance UIDs by searching the information in the
* ReferencedSeriesSequence.
@ -999,6 +1000,9 @@ function _findReferencedSeriesInstanceUIDsFromReferencedImageSequence (
const referencedImageArray = _toArray(metadata.ReferencedImageSequence);
for (let i = 0; i < referencedImageArray.length; i++) {
const { ReferencedSOPInstanceUID } = referencedImageArray[i];
if (!ReferencedSOPInstanceUID) {
continue;
}
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
displaySets,
@ -1033,8 +1037,10 @@ function _findReferencedSeriesInstanceUIDsFromSourceImageSequence (
const firstFunctionalGroups = _toArray(
PerFrameFunctionalGroupsSequence
)[0];
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
if (firstFunctionalGroups) {
const { DerivationImageSequence } = firstFunctionalGroups;
SourceImageSequence = DerivationImageSequence;
}
}
if (!SourceImageSequence) {
@ -1073,9 +1079,16 @@ function _findReferencedSeriesInstanceUIDsFromSOPInstanceUID (
for (let i = 0; i < imageSets.length; i++) {
const { images } = imageSets[i];
if (!images) {
continue;
}
for (let j = 0; j < images.length; j++) {
if (images[j].SOPInstanceUID === SOPInstanceUID) {
return [images[j].getData().metadata.SeriesInstanceUID];
const image = images[j];
if (!image) {
continue;
}
if (image.SOPInstanceUID === SOPInstanceUID) {
return [image.getData().metadata.SeriesInstanceUID];
}
}
}
@ -1085,4 +1098,4 @@ function _toArray(arrayOrObject) {
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
}
export {StudyMetadata, getReferencedDisplaySet};
export {StudyMetadata};

View File

@ -6,7 +6,7 @@ import { ReconstructionIssues } from './../enums.js';
*
* @param {Object[]} An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} value, warningIssues.
* @returns {Object} value, reconstructionIssues.
*/
function isDisplaySetReconstructable(instances) {
if (!instances.length) {
@ -38,11 +38,11 @@ function isDisplaySetReconstructable(instances) {
* Process reconstructable multiframes checks
* TODO: deal with multriframe checks! return false for now as can't reconstruct.
* *
* @returns {Object} value and warningIssues.
* @returns {Object} value and reconstructionIssues.
*/
function processMultiframe() {
const warningIssues = [ReconstructionIssues.MULTIFRAMES];
return { value: false, warningIssues };
const reconstructionIssues = [ReconstructionIssues.MULTIFRAMES];
return { value: false, reconstructionIssues };
}
/**
@ -50,7 +50,7 @@ function processMultiframe() {
*
* @param {Object[]} An array of `OHIFInstanceMetadata` objects.
*
* @returns {Object} value and warningIssues.
* @returns {Object} value and reconstructionIssues.
*/
function processSingleframe(instances) {
const n = instances.length;
@ -60,7 +60,7 @@ function processSingleframe(instances) {
const firstImageSamplesPerPixel = firstImage.SamplesPerPixel;
const firstImageOrientationPatient = firstImage.ImageOrientationPatient;
const warningIssues = [];
const reconstructionIssues = [];
// Can't reconstruct if we:
// -- Have a different dimensions within a displaySet.
// -- Have a different number of components within a displaySet.
@ -75,24 +75,24 @@ function processSingleframe(instances) {
} = instance;
if (Rows !== firstImageRows || Columns !== firstImageColumns) {
warningIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESDIMENSIONS);
} else if (SamplesPerPixel !== firstImageSamplesPerPixel) {
warningIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESCOMPONENTS);
} else if (!_isSameArray(ImageOrientationPatient, firstImageOrientationPatient)) {
warningIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
reconstructionIssues.push(ReconstructionIssues.VARYING_IMAGESORIENTATION);
}
if (warningIssues.length !== 0) {
if (reconstructionIssues.length !== 0) {
break;
}
}
// check if dataset is 4D
if (_isDataset4D(instances)) {
warningIssues.push(ReconstructionIssues.DATASET_4D);
reconstructionIssues.push(ReconstructionIssues.DATASET_4D);
}
return { value: warningIssues.length === 0 ? true : false, warningIssues };
return { value: reconstructionIssues.length === 0 ? true : false, reconstructionIssues };
}
/**
@ -102,14 +102,14 @@ function processSingleframe(instances) {
* @param {Object[]} An array of `OHIFInstanceMetadata` objects.
* @param {boolean} is the dataset 4D.
*
* @returns {Object} isUniform, warningIssues and missingFrames
* @returns {Object} isUniform, reconstructionIssues and missingFrames
*/
function isSpacingUniform(instances, datasetIs4D) {
const n = instances.length;
const firstImage = instances[0].getData().metadata;
const firstImagePositionPatient = firstImage.ImagePositionPatient;
const warningIssues = [];
const reconstructionIssues = [];
let missingFrames = 0;
// Check if frame spacing is approximately equal within a spacingTolerance.
@ -154,7 +154,7 @@ function isSpacingUniform(instances, datasetIs4D) {
if (issue === ReconstructionIssues.MISSING_FRAMES) {
missingFrames += spacingIssue.missingFrames;
} else if (issue === ReconstructionIssues.IRREGULAR_SPACING) {
warningIssues.push(issue);
reconstructionIssues.push(issue);
break;
}
}
@ -164,7 +164,7 @@ function isSpacingUniform(instances, datasetIs4D) {
}
}
return { isUniform: warningIssues.length === 0 ? true : false, missingFrames, warningIssues };
return { isUniform: reconstructionIssues.length === 0 ? true : false, missingFrames, reconstructionIssues };
}

View File

@ -107,7 +107,12 @@ async function loadAndCacheDerivedDisplaySets(referencedDisplaySet, studies, log
});
try {
await recentDisplaySet.load(referencedDisplaySet, studies);
if (recentDisplaySet.hasOwnProperty('getSourceDisplaySet') &&
typeof recentDisplaySet.getSourceDisplaySet === 'function') {
await recentDisplaySet.getSourceDisplaySet(studies);
} else {
await recentDisplaySet.load(referencedDisplaySet, studies);
}
} catch (error) {
recentDisplaySet.isLoaded = false;
recentDisplaySet.loadError = true;

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.1...@ohif/ui@1.9.2) (2021-05-17)
**Note:** Version bump only for package @ohif/ui
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.9.0...@ohif/ui@1.9.1) (2021-03-26)
**Note:** Version bump only for package @ohif/ui

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "1.9.1",
"version": "1.9.2",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",

View File

@ -3,6 +3,7 @@ import './ImageThumbnail.styl';
import { utils } from '@ohif/core';
import React, { useState, useEffect, createRef } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
@ -15,6 +16,7 @@ import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
//import cornerstone from 'cornerstone-core';
function ImageThumbnail(props) {
const {
active,
width,
height,
imageSrc,
@ -97,7 +99,7 @@ function ImageThumbnail(props) {
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
return (
<div className="ImageThumbnail">
<div className={classNames('ImageThumbnail', { active: active })}>
<div className="image-thumbnail-canvas">
{shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} />
@ -126,6 +128,7 @@ function ImageThumbnail(props) {
}
ImageThumbnail.propTypes = {
active: PropTypes.bool,
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
@ -135,6 +138,7 @@ ImageThumbnail.propTypes = {
};
ImageThumbnail.defaultProps = {
active: false,
error: false,
stackPercentComplete: 0,
width: 217,

View File

@ -6,7 +6,7 @@
--sidebar-transition: all 0.3s ease;
}
.ThumbnailEntry.active .ImageThumbnail
.ImageThumbnail.active
border-color: var(--active-color);
box-shadow: none
transition: var(--sidebar-transition);
@ -14,7 +14,7 @@
.ImageThumbnail
background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent
border: 2px solid transparent
border-radius: 12px
height: 135px
margin: 0 auto

View File

@ -20,6 +20,7 @@ function StudyBrowser(props) {
return study.thumbnails.map((thumb, thumbIndex) => {
// TODO: Thumb has more props than we care about?
const {
active,
altImageText,
displaySetInstanceUID,
imageId,
@ -38,6 +39,7 @@ function StudyBrowser(props) {
data-cy="thumbnail-list"
>
<Thumbnail
active={active}
supportsDrag={supportsDrag}
key={`${studyIndex}_${thumbIndex}`}
id={`${studyIndex}_${thumbIndex}`} // Unused?

View File

@ -16,13 +16,13 @@ function ThumbnailFooter({
numImageFrames,
hasWarnings
}) {
const [warningList, warningListSet] = useState([]);
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
useEffect(() => {
let unmounted = false
hasWarnings.then(response => {
if (!unmounted) {
warningListSet(response)
inconsistencyWarningsSet(response)
}
})
return () => {
@ -41,22 +41,22 @@ function ThumbnailFooter({
);
};
const getWarningContent = (warningList) => {
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
const getWarningContent = (inconsistencyWarnings) => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
return <React.Fragment>{inconsistencyWarnings}</React.Fragment>;
}
};
const getWarningInfo = (SeriesNumber, warningList) => {
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
return(
<React.Fragment>
{warningList.length != 0 ? (
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
placement="left"
@ -67,7 +67,7 @@ function ThumbnailFooter({
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
<div className="warningContent">{getWarningContent(warningList)}</div>
<div className="warningContent">{getWarningContent(inconsistencyWarnings)}</div>
</Tooltip>
}
>
@ -87,7 +87,7 @@ function ThumbnailFooter({
SeriesNumber,
InstanceNumber,
numImageFrames,
warningList
inconsistencyWarnings
) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return;
@ -97,7 +97,7 @@ function ThumbnailFooter({
{getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, warningList)}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
</div>
return (seriesInformation);
@ -106,7 +106,7 @@ function ThumbnailFooter({
return (
<div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, warningList)}
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames, inconsistencyWarnings)}
</div>
);
}
@ -159,6 +159,7 @@ function Thumbnail(props) {
{/* SHOW IMAGE */}
{hasImage && (
<ImageThumbnail
active={active}
imageSrc={imageSrc}
imageId={imageId}
error={error}

View File

@ -2,8 +2,15 @@
:root {
--series-count-background-color: #678696;
--active-color: #20A5D6;
}
.thumbnail.active
.alt-image-text
border-color: var(--active-color);
box-shadow: none
transition: var(--sidebar-transition);
.thumbnail
cursor: pointer
display: table
@ -16,7 +23,7 @@
justify-content: center;
background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent;
border: 2px solid transparent;
border-radius: 12px;
height: 135px;
margin: 0 auto;

View File

@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.9.18](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.17...@ohif/viewer@4.9.18) (2021-06-02)
### Bug Fixes
* **dicom-html:** Add parsed dicom meta info section ([#2419](https://github.com/OHIF/Viewers/issues/2419)) ([403688b](https://github.com/OHIF/Viewers/commit/403688b18c52468fc1101166ec0c1734fb710039))
## [4.9.17](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.16...@ohif/viewer@4.9.17) (2021-05-17)
**Note:** Version bump only for package @ohif/viewer
## [4.9.16](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.15...@ohif/viewer@4.9.16) (2021-05-14)
**Note:** Version bump only for package @ohif/viewer
## [4.9.15](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.9.14...@ohif/viewer@4.9.15) (2021-04-22)
**Note:** Version bump only for package @ohif/viewer

View File

@ -91,7 +91,7 @@ describe('OHIF Cornerstone Hotkeys', () => {
':nth-child(2) > .viewport-wrapper > .viewport-element > .ViewportOrientationMarkers.noselect > .left-mid.orientation-marker'
).as('viewport2InfoMidLeft');
cy.get(
':nth-child(2) > .viewport-wrapper > .viewport-element > .ViewportOverlay > div.bottom-right.overlay-element > div'
':nth-child(2) > .viewport-wrapper > .viewport-element > .OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div'
).as('viewport2InfoBottomRight');
// Press multiples hotkeys on viewport #2

View File

@ -741,7 +741,7 @@ describe('OHIF User Preferences', () => {
// Overlay information from 2nd viewport
let second_viewport_overlay =
'div:nth-child(2) > div > div.viewport-element > div.ViewportOverlay > div.bottom-right.overlay-element > div';
'div:nth-child(2) > div > div.viewport-element > div.OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div';
// Shift active viewport to Viewport #2
cy.get('body').type('{rightarrow}');

View File

@ -25,10 +25,10 @@ export function initCommonElementsAliases() {
'.pull-left > .RoundedButtonGroup > .roundedButtonWrapper > .roundedButton'
).as('seriesBtn');
cy.get('section.sidepanel.from-left').as('seriesPanel');
cy.get('div.ViewportOverlay > div.bottom-left.overlay-element > div').as(
cy.get('div.OHIFCornerstoneViewportOverlay > div.bottom-left.overlay-element > div').as(
'viewportInfoBottomLeft'
);
cy.get('div.ViewportOverlay > div.bottom-right.overlay-element > div').as(
cy.get('div.OHIFCornerstoneViewportOverlay > div.bottom-right.overlay-element > div').as(
'viewportInfoBottomRight'
);
cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft');

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "4.9.15",
"version": "4.9.18",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@ -49,18 +49,18 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^2.13.10",
"@ohif/extension-cornerstone": "^2.10.7",
"@ohif/core": "^2.13.12",
"@ohif/extension-cornerstone": "^2.10.9",
"@ohif/extension-debugging": "^0.2.1",
"@ohif/extension-dicom-html": "^1.3.9",
"@ohif/extension-dicom-html": "^1.3.10",
"@ohif/extension-dicom-microscopy": "^0.52.0",
"@ohif/extension-dicom-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.6.11",
"@ohif/extension-dicom-segmentation": "^0.6.8",
"@ohif/extension-dicom-rt": "^0.6.13",
"@ohif/extension-dicom-segmentation": "^0.6.10",
"@ohif/extension-lesion-tracker": "^0.2.1",
"@ohif/extension-vtk": "^1.11.11",
"@ohif/extension-vtk": "^1.11.13",
"@ohif/i18n": "^0.53.0",
"@ohif/ui": "^1.9.1",
"@ohif/ui": "^1.9.2",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
@ -68,7 +68,7 @@
"cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dcmjs": "0.18.5",
"dcmjs": "0.18.8",
"dicom-parser": "^1.8.3",
"dicomweb-client": "^0.4.4",
"hammerjs": "^2.0.8",

View File

@ -1,128 +1,28 @@
window.config = {
// default: '/'
routerBasename: '/',
extensions: [],
showStudyList: true,
filterQueryParam: false,
enableGoogleCloudAdapter: true,
servers: {
dicomWeb: [
{
name: 'DCM4CHEE',
wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
},
],
// This is an array, but we'll only use the first entry for now
dicomWeb: [],
},
// Extensions should be able to suggest default values for these?
// Or we can require that these be explicitly set
hotkeys: [
// ~ Global
// This is an array, but we'll only use the first entry for now
oidc: [
{
commandName: 'incrementActiveViewport',
label: 'Next Viewport',
keys: ['right'],
},
{
commandName: 'decrementActiveViewport',
label: 'Previous Viewport',
keys: ['left'],
},
// Supported Keys: https://craig.is/killing/mice
// ~ Cornerstone Extension
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
label: 'Flip Vertically',
keys: ['v'],
},
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
// clearAnnotations
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
// firstImage
// lastImage
{
commandName: 'previousViewportDisplaySet',
label: 'Previous Series',
keys: ['pagedown'],
},
{
commandName: 'nextViewportDisplaySet',
label: 'Next Series',
keys: ['pageup'],
},
// ~ Cornerstone Tools
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
// ~ Window level presets
{
commandName: 'windowLevelPreset1',
label: 'W/L Preset 1',
keys: ['1'],
},
{
commandName: 'windowLevelPreset2',
label: 'W/L Preset 2',
keys: ['2'],
},
{
commandName: 'windowLevelPreset3',
label: 'W/L Preset 3',
keys: ['3'],
},
{
commandName: 'windowLevelPreset4',
label: 'W/L Preset 4',
keys: ['4'],
},
{
commandName: 'windowLevelPreset5',
label: 'W/L Preset 5',
keys: ['5'],
},
{
commandName: 'windowLevelPreset6',
label: 'W/L Preset 6',
keys: ['6'],
},
{
commandName: 'windowLevelPreset7',
label: 'W/L Preset 7',
keys: ['7'],
},
{
commandName: 'windowLevelPreset8',
label: 'W/L Preset 8',
keys: ['8'],
},
{
commandName: 'windowLevelPreset9',
label: 'W/L Preset 9',
keys: ['9'],
// ~ REQUIRED
// Authorization Server URL
authority: 'https://accounts.google.com',
client_id:
'723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com',
redirect_uri: '/callback', // `OHIFStandaloneViewer.js`
response_type: 'id_token token',
scope:
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
},
],
cornerstoneExtensionConfig: {},
// Following property limits number of simultaneous series metadata requests.
// For http/1.x-only servers, set this to 5 or less to improve
// on first meaningful display in viewer
// If the server is particularly slow to respond to series metadata
// requests as it extracts the metadata from raw files everytime,
// try setting this to even lower value
// Leave it undefined for no limit, sutiable for HTTP/2 enabled servers
// maxConcurrentMetadataRequests: 5,
studyListFunctionsEnabled: true,
};

View File

@ -197,18 +197,38 @@ class Viewer extends Component {
]);
}
const activeViewport = this.props.viewports[this.props.activeViewportIndex];
const activeDisplaySetInstanceUID =
activeViewport ? activeViewport.displaySetInstanceUID : undefined;
this.setState({
thumbnails: _mapStudiesToThumbnails(studies),
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
});
}
}
componentDidUpdate(prevProps) {
const { studies, isStudyLoaded } = this.props;
const {
studies,
isStudyLoaded,
activeViewportIndex,
viewports
} = this.props;
const activeViewport = viewports[activeViewportIndex];
const activeDisplaySetInstanceUID =
activeViewport ? activeViewport.displaySetInstanceUID : undefined;
const prevActiveViewport = prevProps.viewports[prevProps.activeViewportIndex];
const prevActiveDisplaySetInstanceUID =
prevActiveViewport ? prevActiveViewport.displaySetInstanceUID : undefined;
if (studies !== prevProps.studies ||
activeViewportIndex !== prevProps.activeViewportIndex ||
activeDisplaySetInstanceUID !== prevActiveDisplaySetInstanceUID
) {
if (studies !== prevProps.studies) {
this.setState({
thumbnails: _mapStudiesToThumbnails(studies),
thumbnails: _mapStudiesToThumbnails(studies, activeDisplaySetInstanceUID),
});
}
if (isStudyLoaded && isStudyLoaded !== prevProps.isStudyLoaded) {
@ -391,56 +411,63 @@ export default withDialog(Viewer);
* @returns {[string]} an array of strings containing the warnings
*/
const _checkForSeriesInconsistencesWarnings = async function (displaySet, studies) {
const warningsList = [];
if (displaySet.inconsistencyWarnings) {
// warnings already checked and cached in displaySet
return displaySet.inconsistencyWarnings;
}
const inconsistencyWarnings = [];
if (displaySet.Modality !== 'SEG') {
if (displaySet.warningIssues && displaySet.warningIssues.length !== 0) {
displaySet.warningIssues.forEach(warning => {
if (displaySet.reconstructionIssues && displaySet.reconstructionIssues.length !== 0) {
displaySet.reconstructionIssues.forEach(warning => {
switch (warning) {
case ReconstructionIssues.DATASET_4D:
warningsList.push('The dataset is 4D.');
inconsistencyWarnings.push('The dataset is 4D.');
break;
case ReconstructionIssues.VARYING_IMAGESDIMENSIONS:
warningsList.push('The dataset frames have different dimensions (rows, columns).');
inconsistencyWarnings.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).');
inconsistencyWarnings.push('The dataset frames have different components (Sample per pixel).');
break;
case ReconstructionIssues.VARYING_IMAGESORIENTATION:
warningsList.push('The dataset frames have different orientation.');
inconsistencyWarnings.push('The dataset frames have different orientation.');
break;
case ReconstructionIssues.IRREGULAR_SPACING:
warningsList.push('The dataset frames have different pixel spacing.');
inconsistencyWarnings.push('The dataset frames have different pixel spacing.');
break;
case ReconstructionIssues.MULTIFFRAMES:
warningsList.push('The dataset is a multiframes.');
inconsistencyWarnings.push('The dataset is a multiframes.');
break;
default:
break;
}
});
warningsList.push('The datasets is not a reconstructable 3D volume. MPR mode is not available.');
inconsistencyWarnings.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 + '.');
(!displaySet.reconstructionIssues ||
(displaySet.reconstructionIssues && !displaySet.reconstructionIssues.find(warn => warn === ReconstructionIssues.DATASET_4D)))) {
inconsistencyWarnings.push('The datasets is missing frames: ' + displaySet.missingFrames + '.');
}
} else {
const segMetadata = displaySet.metadata;
if (!segMetadata) {
return warningsList;
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
if (!referencedDisplaySet) {
return warningsList;
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
const imageIds = referencedDisplaySet.images.map(image => image.getImageId());
if (!imageIds || imageIds.length === 0) {
return warningsList;
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
for (
@ -462,10 +489,10 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
.SourceImageSequence;
}
if (!SourceImageSequence) {
if (warningsList.length === 0) {
if (inconsistencyWarnings.length === 0) {
const warningMessage = 'The segmentation ' +
'has frames out of plane respect to the source images.';
warningsList.push(warningMessage);
inconsistencyWarnings.push(warningMessage);
}
continue;
}
@ -501,21 +528,70 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
const warningMessage = 'The segmentation ' +
'has frames with different geometry ' +
'dimensions (Rows and Columns) respect to the source images.';
warningsList.push(warningMessage);
inconsistencyWarnings.push(warningMessage);
break;
}
}
if (warningsList.length !== 0) {
if (inconsistencyWarnings.length !== 0) {
const warningMessage = 'The segmentation format is not supported yet. ' +
'The segmentation data (segments) could not be loaded.';
warningsList.push(warningMessage);
inconsistencyWarnings.push(warningMessage);
}
}
return warningsList;
// cache the warnings
displaySet.inconsistencyWarnings = inconsistencyWarnings;
return inconsistencyWarnings;
}
/**
* Checks if display set is active, i.e. if the series is currently shown
* in the active viewport.
*
* For data display set, this functions checks if the active
* display set instance uid in the current active viewport is the same of the
* thumbnail one.
*
* For derived modalities (e.g., SEG and RTSTRUCT), the function gets the
* reference display set and then checks the reference uid with the active
* display set instance uid.
*
* @param {displaySet} displaySet
* @param {Study[]} studies
* @param {string} activeDisplaySetInstanceUID
* @returns {boolean} is active.
*/
const _isDisplaySetActive = function(displaySet, studies, activeDisplaySetInstanceUID) {
let active = false;
const {
displaySetInstanceUID,
} = displaySet;
// TO DO: in the future, we could possibly support new modalities
// we should have a list of all modalities here, instead of having hard coded checks
if (displaySet.Modality !== 'SEG' &&
displaySet.Modality !== 'RTSTRUCT' &&
displaySet.Modality !== 'RTDOSE') {
active = activeDisplaySetInstanceUID === displaySetInstanceUID;
} else if (displaySet.getSourceDisplaySet){
if (displaySet.Modality === 'SEG') {
const { referencedDisplaySet } = displaySet.getSourceDisplaySet(studies, false);
active = referencedDisplaySet ?
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
false;
} else {
const referencedDisplaySet = displaySet.getSourceDisplaySet(studies, false);
active = referencedDisplaySet ?
activeDisplaySetInstanceUID === referencedDisplaySet.displaySetInstanceUID :
false;
}
}
return active;
};
/**
* What types are these? Why do we have "mapping" dropped in here instead of in
* a mapping layer?
@ -524,12 +600,11 @@ const _checkForSeriesInconsistencesWarnings = async function (displaySet, studie
* - Add showStackLoadingProgressBar option
*
* @param {Study[]} studies
* @param {DisplaySet[]} studies[].displaySets
* @param {string} activeDisplaySetInstanceUID
*/
const _mapStudiesToThumbnails = function(studies) {
const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
return studies.map(study => {
const { StudyInstanceUID } = study;
const thumbnails = study.displaySets.map(displaySet => {
const {
displaySetInstanceUID,
@ -549,15 +624,16 @@ const _mapStudiesToThumbnails = function(studies) {
altImageText = 'SEG';
} else if (displaySet.images && displaySet.images.length) {
const imageIndex = Math.floor(displaySet.images.length / 2);
imageId = displaySet.images[imageIndex].getImageId();
} else {
altImageText = displaySet.Modality ? displaySet.Modality : 'UN';
}
const hasWarnings = _checkForSeriesInconsistencesWarnings(displaySet, studies);
const active = _isDisplaySetActive(displaySet, studies, activeDisplaySetInstanceUID)
return {
active,
imageId,
altImageText,
displaySetInstanceUID,

View File

@ -24,6 +24,10 @@ const preloadedState = {
...sessionStorage.loadState(),
};
if (window.config && window.config.disableServersCache === true) {
delete preloadedState.servers;
}
const store = createStore(
rootReducer,
preloadedState,

View File

@ -6619,10 +6619,10 @@ dateformat@^3.0.0:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
dcmjs@0.18.5:
version "0.18.5"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.5.tgz#06edd5562a91fa1e6be3a9fd2fc919e103f44e6f"
integrity sha512-AWGFMwCdQg4C/hJIhnnWS4ieClnWMkVmV9sX29jpLYYMEb7QGBe1t9UsWvgBh1QiXePUh4u+zGFQk27duHB6rw==
dcmjs@0.18.8:
version "0.18.8"
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.8.tgz#fc8e6af03ace9050da49f4108334aa3d0a3bdf3d"
integrity sha512-Z3nNEmzkWdpHQPgjozGqk8XcuhP/hXP1C64Wtg9V+VO9eEc8eNFJFJ6If62f1cuOh0FaRwHqxBT7OzhOAUpFTA==
dependencies:
"@babel/polyfill" "^7.8.3"
"@babel/runtime" "^7.8.4"