IDC2797: improving UI for SR qualitative measurements and bug fixes (#2832)
This commit is contained in:
parent
5d6cf2a609
commit
1bcb0acf9f
@ -237,7 +237,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const warningsOverlay = props => {
|
const overlay = props => {
|
||||||
const displaySet = this.props.viewportData.displaySet;
|
const displaySet = this.props.viewportData.displaySet;
|
||||||
let filteredSRLabels;
|
let filteredSRLabels;
|
||||||
if (displaySet.SRLabels && displaySet.SRLabels.length !== 0) {
|
if (displaySet.SRLabels && displaySet.SRLabels.length !== 0) {
|
||||||
@ -264,7 +264,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
imageIdIndex={currentImageIdIndex}
|
imageIdIndex={currentImageIdIndex}
|
||||||
onNewImageDebounced={newImageHandler}
|
onNewImageDebounced={newImageHandler}
|
||||||
onNewImageDebounceTime={300}
|
onNewImageDebounceTime={300}
|
||||||
viewportOverlayComponent={warningsOverlay}
|
viewportOverlayComponent={overlay}
|
||||||
stackPrefetch={this.props.stackPrefetch}
|
stackPrefetch={this.props.stackPrefetch}
|
||||||
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
|
isStackPrefetchEnabled={this.props.isStackPrefetchEnabled}
|
||||||
// ~~ Connected (From REDUX)
|
// ~~ Connected (From REDUX)
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
.OHIFCornerstoneViewportOverlay .bottom-left3 {
|
.OHIFCornerstoneViewportOverlay .bottom-left3 {
|
||||||
bottom: 110px;
|
bottom: 110px;
|
||||||
left: 100px;
|
left: -20px;
|
||||||
}
|
}
|
||||||
.OHIFCornerstoneViewportOverlay .bottom-right {
|
.OHIFCornerstoneViewportOverlay .bottom-right {
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
|
|||||||
@ -18,19 +18,13 @@ import { Tooltip } from '@ohif/ui/src/components/tooltip';
|
|||||||
import { OverlayTrigger } from '@ohif/ui/src/components/overlayTrigger';
|
import { OverlayTrigger } from '@ohif/ui/src/components/overlayTrigger';
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
background-color: #3f51b5;
|
|
||||||
color: white;
|
color: white;
|
||||||
padding: 5px 15px;
|
padding: 2px 7px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
text-transform: lowercase;
|
text-transform: none;
|
||||||
margin: 5px 5px;
|
margin: 2px 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0px 2px 2px lightgray;
|
|
||||||
transition: ease background-color 250ms;
|
|
||||||
&:hover {
|
|
||||||
background-color: #283593;
|
|
||||||
}
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
@ -152,10 +146,14 @@ class OHIFCornerstoneViewportOverlay extends PureComponent {
|
|||||||
|
|
||||||
const SRLabelsOn = SRLabels && SRLabels.length !== 0 ? true : false;
|
const SRLabelsOn = SRLabels && SRLabels.length !== 0 ? true : false;
|
||||||
|
|
||||||
|
/**/
|
||||||
|
|
||||||
const getSRLabelsContent = SRLabels => {
|
const getSRLabelsContent = SRLabels => {
|
||||||
if (Array.isArray(SRLabels)) {
|
if (Array.isArray(SRLabels)) {
|
||||||
const listedSRLabels = SRLabels.map((SRLabel, index) => {
|
const listedSRLabels = SRLabels.map((SRLabel, index) => {
|
||||||
|
const color = SRLabel.labels.color;
|
||||||
return (
|
return (
|
||||||
|
SRLabel.labels.visible && (
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
key={index}
|
key={index}
|
||||||
placement="top"
|
placement="top"
|
||||||
@ -165,7 +163,10 @@ class OHIFCornerstoneViewportOverlay extends PureComponent {
|
|||||||
className="in tooltip-warning"
|
className="in tooltip-warning"
|
||||||
id="tooltip-top"
|
id="tooltip-top"
|
||||||
>
|
>
|
||||||
<div className="warningTitle"> Designators </div>
|
<div className="warningTitle">
|
||||||
|
{' '}
|
||||||
|
Coding scheme designators{' '}
|
||||||
|
</div>
|
||||||
<div className="warningContent">
|
<div className="warningContent">
|
||||||
{SRLabel.labels.labelCodingSchemeDesignator +
|
{SRLabel.labels.labelCodingSchemeDesignator +
|
||||||
' : ' +
|
' : ' +
|
||||||
@ -175,11 +176,18 @@ class OHIFCornerstoneViewportOverlay extends PureComponent {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'inline-block' }}>
|
<div style={{ display: 'inline-block' }}>
|
||||||
<Button disabled={true} key={index}>
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: color,
|
||||||
|
}}
|
||||||
|
disabled={true}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
{SRLabel.labels.label + ' : ' + SRLabel.labels.value}
|
{SRLabel.labels.label + ' : ' + SRLabel.labels.value}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -128,7 +128,7 @@ const comparisonTimepoints = [
|
|||||||
{
|
{
|
||||||
key: 'Comparison',
|
key: 'Comparison',
|
||||||
date: '15-Jun-18',
|
date: '15-Jun-18',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const MeasurementComparisonTable = () => {
|
const MeasurementComparisonTable = () => {
|
||||||
|
|||||||
@ -128,9 +128,9 @@ const checkIfCanAddMeasurementsToDisplaySet = (
|
|||||||
const imageIds = images.map(i => i.getImageId());
|
const imageIds = images.map(i => i.getImageId());
|
||||||
const SOPInstanceUIDs = images.map(i => i.SOPInstanceUID);
|
const SOPInstanceUIDs = images.map(i => i.SOPInstanceUID);
|
||||||
imageDisplaySet.SRLabels = [];
|
imageDisplaySet.SRLabels = [];
|
||||||
|
const colors = new Map();
|
||||||
measurements.forEach(measurement => {
|
measurements.forEach(measurement => {
|
||||||
const { coords } = measurement;
|
const { coords } = measurement;
|
||||||
|
|
||||||
coords.forEach((coord, index) => {
|
coords.forEach((coord, index) => {
|
||||||
if (coord.ReferencedSOPSequence !== undefined) {
|
if (coord.ReferencedSOPSequence !== undefined) {
|
||||||
const imageIndex = SOPInstanceUIDs.findIndex(
|
const imageIndex = SOPInstanceUIDs.findIndex(
|
||||||
@ -143,13 +143,35 @@ const checkIfCanAddMeasurementsToDisplaySet = (
|
|||||||
const imageMetadata = images[imageIndex].getData().metadata;
|
const imageMetadata = images[imageIndex].getData().metadata;
|
||||||
|
|
||||||
if (coord.GraphicType === 'TEXT') {
|
if (coord.GraphicType === 'TEXT') {
|
||||||
|
const key =
|
||||||
|
measurement.labels[index].label + measurement.labels[index].value;
|
||||||
|
let color = colors.get(key);
|
||||||
|
if (!color) {
|
||||||
|
// random dark color
|
||||||
|
color =
|
||||||
|
'hsla(' + Math.floor(Math.random() * 360) + ', 70%, 30%, 1)';
|
||||||
|
colors.set(key, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
measurement.labels[index].color = color;
|
||||||
|
measurement.isSRText = true;
|
||||||
|
measurement.labels[index].visible = true;
|
||||||
|
|
||||||
imageDisplaySet.SRLabels.push({
|
imageDisplaySet.SRLabels.push({
|
||||||
ReferencedSOPInstanceUID:
|
ReferencedSOPInstanceUID:
|
||||||
coord.ReferencedSOPSequence.ReferencedSOPInstanceUID,
|
coord.ReferencedSOPSequence.ReferencedSOPInstanceUID,
|
||||||
labels: measurement.labels[index],
|
labels: measurement.labels[index],
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
addMeasurement(
|
||||||
|
measurement,
|
||||||
|
imageId,
|
||||||
|
imageMetadata,
|
||||||
|
imageDisplaySet.displaySetInstanceUID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
addMeasurement(
|
addMeasurement(
|
||||||
measurement,
|
measurement,
|
||||||
imageId,
|
imageId,
|
||||||
@ -158,6 +180,7 @@ const checkIfCanAddMeasurementsToDisplaySet = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,8 +26,10 @@ export default function addMeasurement(
|
|||||||
|
|
||||||
const measurementData = {
|
const measurementData = {
|
||||||
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
||||||
|
TrackingIdentifier: measurement.TrackingIdentifier,
|
||||||
renderableData: {},
|
renderableData: {},
|
||||||
labels: measurement.labels,
|
labels: measurement.labels,
|
||||||
|
isSRText: measurement.isSRText,
|
||||||
};
|
};
|
||||||
|
|
||||||
measurement.coords.forEach(coord => {
|
measurement.coords.forEach(coord => {
|
||||||
|
|||||||
@ -6,7 +6,8 @@ export const dicomSRDisplayTool = {
|
|||||||
options: {
|
options: {
|
||||||
measurementTable: {
|
measurementTable: {
|
||||||
displayFunction: data => {
|
displayFunction: data => {
|
||||||
return `(SR) ${data.lesionNamingNumber ||
|
return `(SR) ${data.TrackingIdentifier ||
|
||||||
|
data.lesionNamingNumber ||
|
||||||
data.measurementNumber ||
|
data.measurementNumber ||
|
||||||
data.text ||
|
data.text ||
|
||||||
''}`;
|
''}`;
|
||||||
|
|||||||
@ -9,6 +9,14 @@ import { TableListItem } from './../tableList/TableListItem.js';
|
|||||||
|
|
||||||
import './MeasurementTableItem.styl';
|
import './MeasurementTableItem.styl';
|
||||||
|
|
||||||
|
const ColoredCircle = ({ color }) => {
|
||||||
|
return <div className="item-color" style={{ backgroundColor: color }}></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
ColoredCircle.propTypes = {
|
||||||
|
color: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
class MeasurementTableItem extends Component {
|
class MeasurementTableItem extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
measurementData: PropTypes.object.isRequired,
|
measurementData: PropTypes.object.isRequired,
|
||||||
@ -21,6 +29,14 @@ class MeasurementTableItem extends Component {
|
|||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
collapsed: true,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
warningTitle = '',
|
warningTitle = '',
|
||||||
@ -93,8 +109,93 @@ class MeasurementTableItem extends Component {
|
|||||||
actionButtons.push(deleteButton);
|
actionButtons.push(deleteButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.props.measurementData.isSRText === true &&
|
||||||
|
this.props.measurementData.labels &&
|
||||||
|
this.props.measurementData.labels.length > 0
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TableListItem
|
||||||
|
key={this.props.measurementData.measurementNumber}
|
||||||
|
itemKey={this.props.measurementData.measurementNumber}
|
||||||
|
itemClass={`measurementItem ${this.props.itemClass} ${hasWarningClass}`}
|
||||||
|
itemIndex={this.props.itemIndex}
|
||||||
|
onItemClick={this.onItemClick}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="measurementLocation">
|
||||||
|
{this.props.t(this.props.measurementData.label, {
|
||||||
|
keySeparator: '>',
|
||||||
|
nsSeparator: '|',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="icons">
|
||||||
|
<div className="displayTexts">{this.getDataDisplayText()}</div>
|
||||||
|
<Icon
|
||||||
|
className={`eye-icon`}
|
||||||
|
name={this.state.visible ? 'eye' : 'eye-closed'}
|
||||||
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.measurementData.labels.forEach(label => {
|
||||||
|
label.visible = !this.state.visible;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
visible: !this.state.visible,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
className={`angle-double-${
|
||||||
|
this.state.collapsed ? 'down' : 'up'
|
||||||
|
}`}
|
||||||
|
name={`angle-double-${this.state.collapsed ? 'down' : 'up'}`}
|
||||||
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
collapsed: !this.state.collapsed,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TableListItem>
|
||||||
|
{this.state.collapsed &&
|
||||||
|
this.props.measurementData.labels.map((SRLabel, index) => {
|
||||||
return (
|
return (
|
||||||
<TableListItem
|
<TableListItem
|
||||||
|
key={index}
|
||||||
|
itemKey={index}
|
||||||
|
itemMeta={<ColoredCircle color={SRLabel.color} />}
|
||||||
|
itemMetaClass="item-color-section"
|
||||||
|
onItemClick={this.onItemClick}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="icons">
|
||||||
|
<span>{SRLabel.label + ' : ' + SRLabel.value}</span>
|
||||||
|
<Icon
|
||||||
|
className={`eye-icon`}
|
||||||
|
name={SRLabel.visible ? 'eye' : 'eye-closed'}
|
||||||
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
onClick={() => {
|
||||||
|
SRLabel.visible = !SRLabel.visible;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<TableListItem
|
||||||
|
key={this.props.measurementData.measurementNumber}
|
||||||
itemKey={this.props.measurementData.measurementNumber}
|
itemKey={this.props.measurementData.measurementNumber}
|
||||||
itemClass={`measurementItem ${this.props.itemClass} ${hasWarningClass}`}
|
itemClass={`measurementItem ${this.props.itemClass} ${hasWarningClass}`}
|
||||||
itemIndex={this.props.itemIndex}
|
itemIndex={this.props.itemIndex}
|
||||||
@ -114,6 +215,7 @@ class MeasurementTableItem extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</TableListItem>
|
</TableListItem>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onItemClick = event => {
|
onItemClick = event => {
|
||||||
|
|||||||
@ -1,4 +1,16 @@
|
|||||||
.measurementItem
|
.measurementItem
|
||||||
|
.item-color-section
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.item-color
|
||||||
|
margin-top: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
.rowActions
|
.rowActions
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
background-color: var(--ui-gray-darker)
|
background-color: var(--ui-gray-darker)
|
||||||
@ -63,3 +75,16 @@
|
|||||||
width: 22px;
|
width: 22px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
pointer-events: inherit;
|
pointer-events: inherit;
|
||||||
|
|
||||||
|
.icons
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.icons .eye-icon
|
||||||
|
margin-left: 100px;
|
||||||
|
margin-right: -5px;
|
||||||
|
.icons .angle-double-down
|
||||||
|
.icons .angle-double-up
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: -5px;
|
||||||
|
|||||||
@ -12,16 +12,13 @@ export class TableListItem extends Component {
|
|||||||
itemIndex: PropTypes.number,
|
itemIndex: PropTypes.number,
|
||||||
itemMeta: PropTypes.node,
|
itemMeta: PropTypes.node,
|
||||||
itemMetaClass: PropTypes.string,
|
itemMetaClass: PropTypes.string,
|
||||||
itemKey: PropTypes.oneOfType([
|
itemKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
]),
|
|
||||||
onItemClick: PropTypes.func.isRequired,
|
onItemClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
itemMeta: null,
|
itemMeta: null,
|
||||||
itemMetaClass: ''
|
itemMetaClass: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -122,6 +122,7 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
|
|||||||
|
|
||||||
Object.keys(toolCollections).forEach(toolId => {
|
Object.keys(toolCollections).forEach(toolId => {
|
||||||
const toolMeasurements = toolCollections[toolId];
|
const toolMeasurements = toolCollections[toolId];
|
||||||
|
|
||||||
const tool = tools.find(tool => tool.id === toolId);
|
const tool = tools.find(tool => tool.id === toolId);
|
||||||
const { displayFunction } = tool.options.measurementTable;
|
const { displayFunction } = tool.options.measurementTable;
|
||||||
|
|
||||||
@ -131,12 +132,13 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
|
|||||||
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
|
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
|
||||||
const measurementNumberList =
|
const measurementNumberList =
|
||||||
groupedMeasurements[groupedMeasurementsIndex];
|
groupedMeasurements[groupedMeasurementsIndex];
|
||||||
|
|
||||||
const measurementData = measurementNumberList[0];
|
const measurementData = measurementNumberList[0];
|
||||||
const {
|
const {
|
||||||
measurementNumber,
|
measurementNumber,
|
||||||
lesionNamingNumber,
|
lesionNamingNumber,
|
||||||
toolType,
|
toolType,
|
||||||
isReadOnly
|
isReadOnly,
|
||||||
} = measurementData;
|
} = measurementData;
|
||||||
const measurementId = measurementData._id;
|
const measurementId = measurementData._id;
|
||||||
|
|
||||||
@ -150,6 +152,8 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
|
|||||||
const tableMeasurement = {
|
const tableMeasurement = {
|
||||||
itemNumber: lesionNamingNumber,
|
itemNumber: lesionNamingNumber,
|
||||||
label: getMeasurementText(measurementData),
|
label: getMeasurementText(measurementData),
|
||||||
|
labels: measurementData.labels,
|
||||||
|
isSRText: measurementData.isSRText,
|
||||||
measurementId,
|
measurementId,
|
||||||
measurementNumber,
|
measurementNumber,
|
||||||
lesionNamingNumber,
|
lesionNamingNumber,
|
||||||
|
|||||||
@ -661,10 +661,9 @@ const _mapStudiesToThumbnails = function(studies, activeDisplaySetInstanceUID) {
|
|||||||
let altImageText;
|
let altImageText;
|
||||||
|
|
||||||
if (displaySet.Modality && displaySet.Modality === 'SEG') {
|
if (displaySet.Modality && displaySet.Modality === 'SEG') {
|
||||||
// TODO: We want to replace this with a thumbnail showing
|
|
||||||
// the segmentation map on the image, but this is easier
|
|
||||||
// and better than what we have right now.
|
|
||||||
altImageText = 'SEG';
|
altImageText = 'SEG';
|
||||||
|
} else if (displaySet.Modality && displaySet.Modality === 'SR') {
|
||||||
|
altImageText = 'SR';
|
||||||
} else if (displaySet.images && displaySet.images.length) {
|
} else if (displaySet.images && displaySet.images.length) {
|
||||||
const imageIndex = Math.floor(displaySet.images.length / 2);
|
const imageIndex = Math.floor(displaySet.images.length / 2);
|
||||||
imageId = displaySet.images[imageIndex].getImageId();
|
imageId = displaySet.images[imageIndex].getImageId();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user