feat: Add DICOM SEG support and MPR, referenceLines and StackSync and more (#3015)
* feat: add initial sop class handler for SEG * feat: move segmentation service * update segmentation service methods * fix: viewport data structure * feat: Add initial render for the DICOM SEG for each displaySet * fix: various wrong architectural dependencies between services * feat: initial separate SEG display in each viewport * feat: refactore viewport action bar * initial work for SEG hydration * fix: various bugs regarding drag and dropping different viewports * fix: rendering issues for multiple seg displaysets * fix: bugs for thumbnail and hydration * feat: fix the initial segment color * update after rebase * feat: initial design for the segmentation group table * feat: initial new design for the segmentation panel * feat: segmentation panel * feat: make segmentation appear on all related viewports * fix: segmentation load bug based on functional groups * initial work for segmentation crosshairs * fix: various stylings and functionality * fix: various stylings for the seg panel * fix: overflow styles * feat: add more ui components * feat: added segmentation config * feat: add jump to segment * feat: add jump to segment for DICOM SEG viewport * fix: bugs after rebase * feat: jump in segmentation viewport * fix: mpr support for seg * feat: add more icons * feat: new icons * feat: add new side panel * feat: add segmentation config * feat: add loading indicator to ohif * feat: make hanging protocols follow matching rules for viewports * feat: enhance drag and drop to be hanging protocol aware * fix: mpr restore previous layout * fix: crosshairs toggle * fix: add auth headers to the dicom loader via dicomwebclient * fix: bug for crosshairs toggle in mpr * fix: seg viewport reusing old toolGroup * feat: add loading animation with lottie * fix: various bugs for Segmentation hydration in mpr * feat: change outline alpha to outline opacity * feat: loading indicator for seg viewport * fix: various segmentation group styles * fix: local mode for seg * feat: add animation for the highlight * fix: loading indicatro to show segment indices * feat: enhance modality drop down ui * fix: panels * fix: download form * fix: layout shift in loading indicator * fix: loading indicator to have correct values * fix: update software number * fix: image jump between MPR and default * fix: segmentation cleanup and cine service cleanup * fix: segmentation toolgroup clena up * fix: highlight interval should not trigger again * fix: issue with multiframe sorting * rename: change onSeriesChange to onArrowsClick * fix: buttons for tmtv and layout shift * fix: various bugs wrt crosshairs * fix: crosshairs re init on reset camera * fix: middle slice calculation different from cs middle reset camera * fix: reset camera should reset viewport camera * fix: loading segments in MPR mode * fix: tmtv hp back to before * fix: layout shift * feat: orientation markers for volume viewport * fix: capture for volume viewports * fix: memory leak for back to worklist * fix: loading bar bg color * fix: side panel for only one panel * fix: react select style in production * fix: various styling for segmentation groups * fix: various ui styles * feat: add new segmentation config styles * fix: hover state for segmentation item * fix: side panel layout shift * temp add panels * try to fix scrollbar for thimbnail * fix: scroll not appearing for side panels * feat: changed styles for scrollbar * feat: add cpu fallback warning * fix: webworker destroy * fix: select styles * fix: orientation marker color and position * fix: capture screenshot for volume viewports * fix: hide segment visibility on mpr * fix: reloading an already loaded seg displayset * feat: add is equal * fix: mpr jump to measurements * apply review comments * fix: optimization for the segmentation load * fix: remove unnecessary context menu and hp service reset * fix: segmentation service wrt brush settings * feat: initial work for the stack synchronization * fix: add validateDisplaySetSelectorsForNewDisplaySets to make sure drag and drop can follow requirements * fix: various react proptypes * fix: thumbnails for the tmtv and change default props to default params * feat: make showing loading indicator configurable and add docs * bump versions and add more docs * fix demo * update cornerstone versions * apply review comments * feat: add reference lines * fix build * fix unit tests * add reference lines icon * fix: e2e tests * fix static wado config * docs: add segmentation service docs * fix: docker build * fix: keep camera and bump versions * Try re-enabling minification to fix deploy previews Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
This commit is contained in:
1 parent
08c58b0634
commit
83ea228d86
254 files changed
+12856
-3141
No files matched your search
@@ -1,35 +1,35 @@
|
||||
import React from 'react'
|
||||
import ViewportActionBar from './ViewportActionBar'
|
||||
import React from 'react';
|
||||
import ViewportActionBar from './ViewportActionBar';
|
||||
|
||||
export default {
|
||||
component: ViewportActionBar,
|
||||
title: 'Components/ViewportActionBar',
|
||||
}
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<div className="w-1/2 p-4 h-64">
|
||||
<ViewportActionBar
|
||||
onSeriesChange={direction => alert(`Series ${direction}`)}
|
||||
studyData={{
|
||||
label: 'A',
|
||||
isTracked: false,
|
||||
isLocked: false,
|
||||
isRehydratable: true,
|
||||
studyDate: '07-Sep-2010',
|
||||
currentSeries: 1,
|
||||
seriesDescription:
|
||||
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
|
||||
modality: 'CT',
|
||||
patientInformation: {
|
||||
patientName: 'Smith, Jane',
|
||||
patientSex: 'F',
|
||||
patientAge: '59',
|
||||
MRN: '10000001',
|
||||
thickness: '2.0mm',
|
||||
spacing: '1.25mm',
|
||||
scanner: 'Aquilion',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<ViewportActionBar
|
||||
onArrowsClick={direction => alert(`Series ${direction}`)}
|
||||
studyData={{
|
||||
label: 'A',
|
||||
isTracked: false,
|
||||
isLocked: false,
|
||||
isRehydratable: true,
|
||||
studyDate: '07-Sep-2010',
|
||||
currentSeries: 1,
|
||||
seriesDescription:
|
||||
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
|
||||
modality: 'CT',
|
||||
patientInformation: {
|
||||
patientName: 'Smith, Jane',
|
||||
patientSex: 'F',
|
||||
patientAge: '59',
|
||||
MRN: '10000001',
|
||||
thickness: '2.0mm',
|
||||
spacing: '1.25mm',
|
||||
scanner: 'Aquilion',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -1,18 +1,10 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Icon, ButtonGroup, Button, Tooltip, CinePlayer } from '../';
|
||||
import { Icon, ButtonGroup, Button, CinePlayer } from '../';
|
||||
import useOnClickOutside from '../../utils/useOnClickOutside';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PatientInfo from '../PatientInfo';
|
||||
import { StringNumber } from '../../types';
|
||||
|
||||
const classes = {
|
||||
infoHeader: 'text-base text-primary-light',
|
||||
infoText: 'text-base text-white max-w-24 truncate',
|
||||
firstRow: 'flex flex-col',
|
||||
row: 'flex flex-col ml-4',
|
||||
};
|
||||
|
||||
const ViewportActionBar = ({
|
||||
studyData,
|
||||
showNavArrows,
|
||||
@@ -20,27 +12,15 @@ const ViewportActionBar = ({
|
||||
showCine,
|
||||
cineProps,
|
||||
showPatientInfo: patientInfoVisibility,
|
||||
onSeriesChange,
|
||||
onArrowsClick,
|
||||
onDoubleClick,
|
||||
//
|
||||
onPillClick,
|
||||
getStatusComponent,
|
||||
}) => {
|
||||
const [showPatientInfo, setShowPatientInfo] = useState(patientInfoVisibility);
|
||||
|
||||
// TODO -> Remake this component with a bunch of generic slots that can be filled,
|
||||
// Its not generic at all, isTracked etc shouldn't be parts of this component.
|
||||
// It shouldn't care that a tracking mode or SR exists.
|
||||
// Things like the right/left buttons should be made into smaller
|
||||
// Components you can compose.
|
||||
// OHIF-200 ticket.
|
||||
|
||||
const {
|
||||
label,
|
||||
isTracked,
|
||||
isLocked,
|
||||
isRehydratable,
|
||||
useAltStyling,
|
||||
modality,
|
||||
studyDate,
|
||||
currentSeries,
|
||||
seriesDescription,
|
||||
@@ -75,174 +55,10 @@ const ViewportActionBar = ({
|
||||
return () => clickOutsideListener.remove();
|
||||
}, [clickOutsideListener, showPatientInfo]);
|
||||
|
||||
const renderIconStatus = () => {
|
||||
if (modality === 'SR') {
|
||||
// 1 - Incompatible
|
||||
// 2 - Locked
|
||||
// 3 - Rehydratable / Open
|
||||
const state =
|
||||
isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1;
|
||||
let ToolTipMessage = null;
|
||||
let StatusIcon = null;
|
||||
|
||||
switch (state) {
|
||||
case 1:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 rounded-full"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
backgroundColor: '#98e5c1',
|
||||
border: 'solid 1.5px #000000',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="exclamation"
|
||||
style={{ color: '#000', width: '12px', height: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => (
|
||||
<div>
|
||||
This structured report is not compatible
|
||||
<br />
|
||||
with this application.
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 bg-black rounded-full"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="lock"
|
||||
style={{ color: '#05D97C', width: '8px', height: '11px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => (
|
||||
<div>
|
||||
This structured report is currently read-only
|
||||
<br />
|
||||
because you are tracking measurements in
|
||||
<br />
|
||||
another viewport.
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 bg-white rounded-full group-hover:bg-customblue-200"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
border: 'solid 1.5px #000000',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
style={{ color: '#000', width: '14px', height: '14px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => <div>Click to restore measurements.</div>;
|
||||
}
|
||||
|
||||
const StatusPill = () => (
|
||||
<div
|
||||
className={classnames(
|
||||
'group relative flex items-center justify-center px-2 rounded-full cursor-default bg-customgreen-100',
|
||||
{
|
||||
'hover:bg-customblue-100': state === 3,
|
||||
'cursor-pointer': state === 3,
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
height: '24px',
|
||||
width: '55px',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (state === 3) {
|
||||
// TODO: Gatsby build failing due
|
||||
// to ESLint's "no-unused-expressions"
|
||||
//onPillClick?.();
|
||||
if (onPillClick) {
|
||||
onPillClick();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="pr-1 text-lg font-bold leading-none text-black">
|
||||
SR
|
||||
</span>
|
||||
<StatusIcon />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ToolTipMessage && (
|
||||
<Tooltip content={<ToolTipMessage />} position="bottom-left">
|
||||
<StatusPill />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!ToolTipMessage && <StatusPill />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const trackedIcon = isTracked ? 'tracked' : 'dotted-circle';
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Tooltip
|
||||
position="bottom-left"
|
||||
content={
|
||||
<div className="flex py-2">
|
||||
<div className="flex pt-1">
|
||||
<Icon name="info-link" className="w-4 text-primary-main" />
|
||||
</div>
|
||||
<div className="flex ml-4">
|
||||
<span className="text-base text-common-light">
|
||||
{isTracked ? (
|
||||
<>
|
||||
Series is
|
||||
<span className="font-bold text-white"> tracked</span> and
|
||||
can be viewed <br /> in the measurement panel
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Measurements for
|
||||
<span className="font-bold text-white"> untracked </span>
|
||||
series <br /> will not be shown in the <br /> measurements
|
||||
panel
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name={trackedIcon} className="w-6 text-primary-light" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const borderColor = useAltStyling ? '#365A6A' : '#1D205A';
|
||||
|
||||
let backgroundColor = '#020424';
|
||||
if (useAltStyling || isTracked) {
|
||||
if (useAltStyling) {
|
||||
backgroundColor = '#031923';
|
||||
}
|
||||
|
||||
@@ -254,11 +70,12 @@ const ViewportActionBar = ({
|
||||
backgroundColor: backgroundColor,
|
||||
}}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
>
|
||||
<div className="flex flex-1 grow mt-2 min-w-48">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-white text-large">{label}</span>
|
||||
{showStatus && renderIconStatus()}
|
||||
{showStatus && getStatusComponent()}
|
||||
</div>
|
||||
<div className="flex flex-col justify-start ml-4">
|
||||
<div className="flex">
|
||||
@@ -288,7 +105,7 @@ const ViewportActionBar = ({
|
||||
size="initial"
|
||||
className="px-2 py-1 bg-black"
|
||||
border="light"
|
||||
onClick={() => onSeriesChange('left')}
|
||||
onClick={() => onArrowsClick('left')}
|
||||
>
|
||||
<Icon name="chevron-left" className="w-4 text-white" />
|
||||
</Button>
|
||||
@@ -296,7 +113,7 @@ const ViewportActionBar = ({
|
||||
size="initial"
|
||||
border="light"
|
||||
className="px-2 py-1 bg-black"
|
||||
onClick={() => onSeriesChange('right')}
|
||||
onClick={() => onArrowsClick('right')}
|
||||
>
|
||||
<Icon name="chevron-right" className="w-4 text-white" />
|
||||
</Button>
|
||||
@@ -326,7 +143,7 @@ const ViewportActionBar = ({
|
||||
};
|
||||
|
||||
ViewportActionBar.propTypes = {
|
||||
onSeriesChange: PropTypes.func.isRequired,
|
||||
onArrowsClick: PropTypes.func.isRequired,
|
||||
showNavArrows: PropTypes.bool,
|
||||
showCine: PropTypes.bool,
|
||||
cineProps: PropTypes.object,
|
||||
@@ -336,12 +153,9 @@ ViewportActionBar.propTypes = {
|
||||
useAltStyling: PropTypes.bool,
|
||||
//
|
||||
label: PropTypes.string.isRequired,
|
||||
isTracked: PropTypes.bool.isRequired,
|
||||
isRehydratable: PropTypes.bool.isRequired,
|
||||
studyDate: PropTypes.string.isRequired,
|
||||
currentSeries: StringNumber.isRequired,
|
||||
seriesDescription: PropTypes.string.isRequired,
|
||||
modality: PropTypes.string.isRequired,
|
||||
patientInformation: PropTypes.shape({
|
||||
patientName: PropTypes.string.isRequired,
|
||||
patientSex: PropTypes.string.isRequired,
|
||||
@@ -352,6 +166,7 @@ ViewportActionBar.propTypes = {
|
||||
scanner: PropTypes.string.isRequired,
|
||||
}),
|
||||
}).isRequired,
|
||||
getStatusComponent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ViewportActionBar.defaultProps = {
|
||||
@@ -362,127 +177,4 @@ ViewportActionBar.defaultProps = {
|
||||
showPatientInfo: false,
|
||||
};
|
||||
|
||||
function PatientInfo({
|
||||
patientName,
|
||||
patientSex,
|
||||
patientAge,
|
||||
MRN,
|
||||
thickness,
|
||||
spacing,
|
||||
scanner,
|
||||
isOpen,
|
||||
showPatientInfoRef,
|
||||
}) {
|
||||
const { t } = useTranslation('PatientInfo');
|
||||
|
||||
while (patientAge.charAt(0) === '0') {
|
||||
patientAge = patientAge.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={showPatientInfoRef}>
|
||||
<Tooltip
|
||||
isSticky
|
||||
isDisabled={!isOpen}
|
||||
position="bottom-right"
|
||||
content={
|
||||
isOpen && (
|
||||
<div className="flex py-2">
|
||||
<div className="flex pt-1">
|
||||
<Icon name="info-link" className="w-4 text-primary-main" />
|
||||
</div>
|
||||
<div className="flex flex-col ml-2">
|
||||
<span
|
||||
className="text-base font-bold text-white"
|
||||
title={patientName}
|
||||
>
|
||||
{patientName}
|
||||
</span>
|
||||
<div className="flex pb-4 mt-4 mb-4 border-b border-secondary-main">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Sex')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientSex}
|
||||
>
|
||||
{patientSex}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Age')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientAge}
|
||||
>
|
||||
{patientAge}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('MRN')}
|
||||
</span>
|
||||
<span className={classnames(classes.infoText)} title={MRN}>
|
||||
{MRN}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Thickness')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={thickness}
|
||||
>
|
||||
{thickness}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Spacing')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={spacing}
|
||||
>
|
||||
{spacing}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Scanner')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={scanner}
|
||||
>
|
||||
{scanner}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="relative flex justify-end cursor-pointer">
|
||||
<div className="relative">
|
||||
<Icon name="profile" className="w-5 text-white" />
|
||||
<Icon
|
||||
name="info-link"
|
||||
className="absolute w-5 text-white bg-black"
|
||||
style={{ right: -7, bottom: -10 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewportActionBar;
|
||||
Reference in new issue
Block a user