Add badge numbers for seg and rtstruct
This commit is contained in:
parent
2afcd498f3
commit
3f40cdced7
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
||||||
import id from './id.js';
|
import id from './id.js';
|
||||||
import RTPanel from './components/RTPanel/RTPanel';
|
import RTPanel from './components/RTPanel/RTPanel';
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
|
||||||
import { utils } from '@ohif/core';
|
|
||||||
const { studyMetadataManager } = utils;
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -42,12 +43,34 @@ export default {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onRTStructsLoaded = ({ detail }) => {
|
||||||
|
const { rtStructDisplaySet, referencedDisplaySet } = detail;
|
||||||
|
|
||||||
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
rtStructDisplaySet.StudyInstanceUID
|
||||||
|
);
|
||||||
|
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||||
|
referencedSeriesInstanceUID: referencedDisplaySet.SeriesInstanceUID,
|
||||||
|
Modality: 'RTSTRUCT',
|
||||||
|
});
|
||||||
|
const event = new CustomEvent('rt-panel-updated', {
|
||||||
|
detail: {
|
||||||
|
badgeNumber: referencedDisplaysets.length,
|
||||||
|
target: 'rt-panel',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('extensiondicomrtrtloaded', onRTStructsLoaded);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
label: 'RTSTRUCT',
|
label: 'RTSTRUCT',
|
||||||
target: 'rt-panel',
|
target: 'rt-panel',
|
||||||
|
stateEvent: 'rt-panel-updated',
|
||||||
isDisabled: (studies, activeViewport) => {
|
isDisabled: (studies, activeViewport) => {
|
||||||
if (!studies) {
|
if (!studies) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -90,11 +90,11 @@ export default async function loadRTStruct(
|
|||||||
const sopInstanceUID = ContourImageSequence
|
const sopInstanceUID = ContourImageSequence
|
||||||
? ContourImageSequence.ReferencedSOPInstanceUID
|
? ContourImageSequence.ReferencedSOPInstanceUID
|
||||||
: _getClosestSOPInstanceUID(
|
: _getClosestSOPInstanceUID(
|
||||||
ContourData,
|
ContourData,
|
||||||
ContourGeometricType,
|
ContourGeometricType,
|
||||||
NumberOfContourPoints,
|
NumberOfContourPoints,
|
||||||
imageIdSopInstanceUidPairs
|
imageIdSopInstanceUidPairs
|
||||||
);
|
);
|
||||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||||
|
|
||||||
if (!imageId) {
|
if (!imageId) {
|
||||||
@ -171,7 +171,13 @@ export default async function loadRTStruct(
|
|||||||
* allows us to easily watch the module or the rtstruct loading process in any other component
|
* allows us to easily watch the module or the rtstruct loading process in any other component
|
||||||
* without subscribing to external events.
|
* without subscribing to external events.
|
||||||
*/
|
*/
|
||||||
const event = new CustomEvent('extensiondicomrtrtloaded');
|
const event = new CustomEvent('extensiondicomrtrtloaded', {
|
||||||
|
detail: {
|
||||||
|
rtStructDisplaySet,
|
||||||
|
referencedDisplaySet,
|
||||||
|
studies,
|
||||||
|
},
|
||||||
|
});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,7 +305,9 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
|||||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
return imageIdSopInstanceUidPairsEntry
|
||||||
|
? imageIdSopInstanceUidPairsEntry.imageId
|
||||||
|
: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||||
|
|||||||
@ -224,16 +224,20 @@ const SegmentationPanel = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, [activeIndex, viewports]);
|
}, [
|
||||||
|
activeIndex,
|
||||||
|
updateSegmentationComboBox,
|
||||||
|
viewports,
|
||||||
|
]);
|
||||||
|
|
||||||
const updateSegmentationComboBox = (e) => {
|
const updateSegmentationComboBox = e => {
|
||||||
const index = e.detail.activatedLabelmapIndex;
|
const index = e.detail.activatedLabelmapIndex;
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
setState(state => ({ ...state, selectedSegmentation: index }));
|
setState(state => ({ ...state, selectedSegmentation: index }));
|
||||||
} else {
|
} else {
|
||||||
cleanSegmentationComboBox();
|
cleanSegmentationComboBox();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanSegmentationComboBox = () => {
|
const cleanSegmentationComboBox = () => {
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
@ -245,7 +249,7 @@ const SegmentationPanel = ({
|
|||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
selectedSegmentation: -1,
|
selectedSegmentation: -1,
|
||||||
}));
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
const refreshSegmentations = () => {
|
const refreshSegmentations = () => {
|
||||||
const activeViewport = getActiveViewport();
|
const activeViewport = getActiveViewport();
|
||||||
@ -309,7 +313,8 @@ const SegmentationPanel = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
||||||
(segDisplay => segDisplay.loadError !== true));
|
segDisplay => segDisplay.loadError !== true
|
||||||
|
);
|
||||||
|
|
||||||
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import OHIF from '@ohif/core';
|
||||||
|
|
||||||
import init from './init.js';
|
import init from './init.js';
|
||||||
import toolbarModule from './toolbarModule.js';
|
import toolbarModule from './toolbarModule.js';
|
||||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
const { studyMetadataManager } = OHIF.utils;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
@ -79,12 +82,36 @@ export default {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSegmentationsLoaded = ({ detail }) => {
|
||||||
|
const { segDisplaySet, segMetadata } = detail;
|
||||||
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
segDisplaySet.StudyInstanceUID
|
||||||
|
);
|
||||||
|
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||||
|
referencedSeriesInstanceUID: segMetadata.seriesInstanceUid,
|
||||||
|
Modality: 'SEG',
|
||||||
|
});
|
||||||
|
const event = new CustomEvent('segmentation-panel-updated', {
|
||||||
|
detail: {
|
||||||
|
badgeNumber: referencedDisplaysets.length,
|
||||||
|
target: 'segmentation-panel',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'extensiondicomsegmentationsegloaded',
|
||||||
|
onSegmentationsLoaded
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
label: 'Segmentations',
|
label: 'Segmentations',
|
||||||
target: 'segmentation-panel',
|
target: 'segmentation-panel',
|
||||||
|
stateEvent: 'segmentation-panel-updated',
|
||||||
isDisabled: studies => {
|
isDisabled: studies => {
|
||||||
if (!studies) {
|
if (!studies) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -59,7 +59,16 @@ export default async function loadSegmentation(
|
|||||||
* without subscribing to external events.
|
* without subscribing to external events.
|
||||||
*/
|
*/
|
||||||
console.log('Segmentation loaded.');
|
console.log('Segmentation loaded.');
|
||||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
const event = new CustomEvent('extensiondicomsegmentationsegloaded', {
|
||||||
|
detail: {
|
||||||
|
imageIds,
|
||||||
|
segDisplaySet,
|
||||||
|
labelmapBuffer,
|
||||||
|
segMetadata,
|
||||||
|
segmentsOnFrame,
|
||||||
|
labelmapSegments,
|
||||||
|
},
|
||||||
|
});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
return labelmapIndex;
|
return labelmapIndex;
|
||||||
|
|||||||
@ -260,7 +260,6 @@ class MeasurementService {
|
|||||||
const sourceInfo = this._getSourceInfo(source);
|
const sourceInfo = this._getSourceInfo(source);
|
||||||
|
|
||||||
if (!definition) {
|
if (!definition) {
|
||||||
console.log('TEST');
|
|
||||||
log.warn('No source definition provided. Exiting early.');
|
log.warn('No source definition provided. Exiting early.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,29 @@
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoundedButtonGroup .badgeNumber-container {
|
||||||
|
background-color: var(--ui-border-color-dark);
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 5px;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoundedButtonGroup .badgeNumber {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
.roundedButtonWrapper {
|
.roundedButtonWrapper {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -36,7 +59,12 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
|
.roundedButtonWrapper
|
||||||
|
.roundedButton
|
||||||
|
svg
|
||||||
|
.roundedButtonWrapper
|
||||||
|
.roundedButton
|
||||||
|
span {
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ class RoundedButtonGroup extends Component {
|
|||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
|
stateEvent: PropTypes.string,
|
||||||
icon: PropTypes.oneOfType([
|
icon: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
@ -30,6 +31,15 @@ class RoundedButtonGroup extends Component {
|
|||||||
value: null,
|
value: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
badgeNumbers: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.onStateEvent = this.onStateEvent.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
onClickOption = value => {
|
onClickOption = value => {
|
||||||
let newValue = value;
|
let newValue = value;
|
||||||
if (this.props.value === value) {
|
if (this.props.value === value) {
|
||||||
@ -41,6 +51,48 @@ class RoundedButtonGroup extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onStateEvent(event) {
|
||||||
|
const optionIndex = this.props.options.findIndex(
|
||||||
|
o => o.value === event.detail.target
|
||||||
|
);
|
||||||
|
if (optionIndex > -1) {
|
||||||
|
const badgeNumbers = this.state.badgeNumbers;
|
||||||
|
badgeNumbers[optionIndex] = event.detail.badgeNumber;
|
||||||
|
this.setState({ badgeNumbers });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.options.forEach(option => {
|
||||||
|
if (option.stateEvent) {
|
||||||
|
document.addEventListener(option.stateEvent, this.onStateEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
this.props.options.forEach((option, index) => {
|
||||||
|
if (
|
||||||
|
option.stateEvent &&
|
||||||
|
option.stateEvent !==
|
||||||
|
(prevProps.options[index]
|
||||||
|
? prevProps.options[index].stateEvent
|
||||||
|
: null)
|
||||||
|
) {
|
||||||
|
document.removeEventListener(option.stateEvent, this.onStateEvent);
|
||||||
|
document.addEventListener(option.stateEvent, this.onStateEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.options.forEach(option => {
|
||||||
|
if (option.stateEvent) {
|
||||||
|
document.removeEventListener(option.stateEvent, this.onStateEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let className = classnames(
|
let className = classnames(
|
||||||
RoundedButtonGroup.className,
|
RoundedButtonGroup.className,
|
||||||
@ -62,6 +114,14 @@ class RoundedButtonGroup extends Component {
|
|||||||
<div className="bottomLabel">{option.bottomLabel}</div>
|
<div className="bottomLabel">{option.bottomLabel}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let badgeNumber = this.state.badgeNumbers[index];
|
||||||
|
const badgeNumberOverflow = String(badgeNumber).length > 2;
|
||||||
|
badgeNumber = badgeNumber
|
||||||
|
? badgeNumberOverflow
|
||||||
|
? 99
|
||||||
|
: badgeNumber
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
@ -70,6 +130,14 @@ class RoundedButtonGroup extends Component {
|
|||||||
>
|
>
|
||||||
<div className="roundedButton">
|
<div className="roundedButton">
|
||||||
{optionText}
|
{optionText}
|
||||||
|
{badgeNumber && (
|
||||||
|
<div className="badgeNumber-container">
|
||||||
|
<span className="badgeNumber">
|
||||||
|
{badgeNumber}
|
||||||
|
{badgeNumberOverflow && '+'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{iconProps && <Icon {...iconProps} />}
|
{iconProps && <Icon {...iconProps} />}
|
||||||
</div>
|
</div>
|
||||||
{bottomLabel}
|
{bottomLabel}
|
||||||
|
|||||||
@ -96,6 +96,8 @@ class ToolbarRow extends Component {
|
|||||||
value: menuOption.target,
|
value: menuOption.target,
|
||||||
icon: menuOption.icon,
|
icon: menuOption.icon,
|
||||||
bottomLabel: menuOption.label,
|
bottomLabel: menuOption.label,
|
||||||
|
badgeNumber: menuOption.badgeNumber,
|
||||||
|
stateEvent: menuOption.stateEvent,
|
||||||
};
|
};
|
||||||
const from = menuOption.from || 'right';
|
const from = menuOption.from || 'right';
|
||||||
|
|
||||||
@ -110,6 +112,10 @@ class ToolbarRow extends Component {
|
|||||||
icon: 'th-large',
|
icon: 'th-large',
|
||||||
bottomLabel: this.props.t('Series'),
|
bottomLabel: this.props.t('Series'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// this.setState({
|
||||||
|
// toolbarButtons: _getVisibleToolbarButtons.call(this),
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user