2021-01-16 01:19:12 -03:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2020-03-13 16:45:26 -03:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
import cornerstoneTools from 'cornerstone-tools';
|
|
|
|
|
import cornerstone from 'cornerstone-core';
|
|
|
|
|
import moment from 'moment';
|
2021-01-16 01:19:12 -03:00
|
|
|
import classNames from 'classnames';
|
2020-03-13 16:45:26 -03:00
|
|
|
import { utils, log } from '@ohif/core';
|
|
|
|
|
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
2020-09-10 11:11:59 +02:00
|
|
|
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2020-08-10 11:20:31 +02:00
|
|
|
import setActiveLabelmap from '../../utils/setActiveLabelMap';
|
|
|
|
|
import refreshViewports from '../../utils/refreshViewports';
|
|
|
|
|
|
2020-03-13 16:45:26 -03:00
|
|
|
import {
|
|
|
|
|
BrushColorSelector,
|
|
|
|
|
BrushRadius,
|
|
|
|
|
SegmentationItem,
|
|
|
|
|
SegmentItem,
|
|
|
|
|
SegmentationSelect,
|
|
|
|
|
} from '../index';
|
|
|
|
|
|
|
|
|
|
import './SegmentationPanel.css';
|
|
|
|
|
import SegmentationSettings from '../SegmentationSettings/SegmentationSettings';
|
|
|
|
|
|
|
|
|
|
const { studyMetadataManager } = utils;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SegmentationPanel component
|
|
|
|
|
*
|
2020-04-24 07:25:05 -03:00
|
|
|
* @param {Array} props.studies - Studies data
|
|
|
|
|
* @param {Array} props.viewports - Viewports data (viewportSpecificData)
|
|
|
|
|
* @param {number} props.activeIndex - Active viewport index
|
|
|
|
|
* @param {boolean} props.isOpen - Boolean that indicates if the panel is expanded
|
2020-05-06 14:17:44 -03:00
|
|
|
* @param {Function} props.onSegmentItemClick - Segment click handler
|
|
|
|
|
* @param {Function} props.onSegmentVisibilityChange - Segment visibiliy change handler
|
|
|
|
|
* @param {Function} props.onConfigurationChange - Configuration change handler
|
|
|
|
|
* @param {Function} props.activeContexts - List of active application contexts
|
|
|
|
|
* @param {Function} props.contexts - List of available application contexts
|
2020-03-13 16:45:26 -03:00
|
|
|
* @returns component
|
|
|
|
|
*/
|
2020-04-24 07:25:05 -03:00
|
|
|
const SegmentationPanel = ({
|
|
|
|
|
studies,
|
|
|
|
|
viewports,
|
|
|
|
|
activeIndex,
|
|
|
|
|
isOpen,
|
2020-05-06 14:17:44 -03:00
|
|
|
onSegmentItemClick,
|
|
|
|
|
onSegmentVisibilityChange,
|
|
|
|
|
onConfigurationChange,
|
|
|
|
|
onDisplaySetLoadFailure,
|
|
|
|
|
onSelectedSegmentationChange,
|
2020-06-04 06:52:30 -03:00
|
|
|
activeContexts = [],
|
|
|
|
|
contexts = {},
|
2020-04-24 07:25:05 -03:00
|
|
|
}) => {
|
2020-05-06 14:17:44 -03:00
|
|
|
const isVTK = () => activeContexts.includes(contexts.VTK);
|
|
|
|
|
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
|
|
|
|
|
2020-03-13 16:45:26 -03:00
|
|
|
/*
|
|
|
|
|
* TODO: wrap get/set interactions with the cornerstoneTools
|
|
|
|
|
* store with context to make these kind of things less blurry.
|
|
|
|
|
*/
|
2020-04-01 00:54:02 -03:00
|
|
|
const { configuration } = cornerstoneTools.getModule('segmentation');
|
2020-03-13 16:45:26 -03:00
|
|
|
const DEFAULT_BRUSH_RADIUS = configuration.radius || 10;
|
2020-05-06 14:17:44 -03:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* TODO: We shouldn't hardcode brushColor color, in the future
|
|
|
|
|
* the SEG may set the colorLUT to whatever it wants.
|
|
|
|
|
*/
|
2020-04-01 00:54:02 -03:00
|
|
|
const [state, setState] = useState({
|
|
|
|
|
brushRadius: DEFAULT_BRUSH_RADIUS,
|
2020-08-10 11:20:31 +02:00
|
|
|
brushColor: 'rgba(221, 85, 85, 1)',
|
2021-01-16 01:19:12 -03:00
|
|
|
selectedSegment: 0,
|
|
|
|
|
selectedSegmentation: 0,
|
|
|
|
|
showSettings: false,
|
|
|
|
|
labelMapList: [],
|
2020-04-24 07:25:05 -03:00
|
|
|
segmentList: [],
|
2021-01-16 01:19:12 -03:00
|
|
|
segmentsHidden: [],
|
|
|
|
|
segmentNumbers: [],
|
2020-06-15 12:41:36 -03:00
|
|
|
isLoading: false,
|
2020-08-10 11:20:31 +02:00
|
|
|
isDisabled: true,
|
2020-04-01 00:54:02 -03:00
|
|
|
});
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const getActiveViewport = () => viewports[activeIndex];
|
|
|
|
|
|
|
|
|
|
const getFirstImageId = () => {
|
|
|
|
|
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
|
|
|
|
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
|
|
|
|
return studyMetadata.getFirstImageId(displaySetInstanceUID);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveLabelMaps3D = () => {
|
|
|
|
|
const { labelmaps3D, activeLabelmapIndex } = getBrushStackState();
|
|
|
|
|
return labelmaps3D[activeLabelmapIndex];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveLabelMapIndex = () => {
|
|
|
|
|
const { activeLabelmapIndex } = getBrushStackState();
|
|
|
|
|
return activeLabelmapIndex;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveSegmentIndex = () => {
|
|
|
|
|
const { activeSegmentIndex } = getActiveLabelMaps3D();
|
|
|
|
|
return activeSegmentIndex;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveLabelMaps2D = () => {
|
|
|
|
|
const { labelmaps2D } = getActiveLabelMaps3D();
|
|
|
|
|
return labelmaps2D;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getCurrentDisplaySet = () => {
|
|
|
|
|
const { StudyInstanceUID, displaySetInstanceUID } = getActiveViewport();
|
|
|
|
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
|
|
|
|
const allDisplaySets = studyMetadata.getDisplaySets();
|
|
|
|
|
return allDisplaySets.find(
|
|
|
|
|
ds => ds.displaySetInstanceUID === displaySetInstanceUID
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const setActiveSegment = segmentIndex => {
|
|
|
|
|
const activeSegmentIndex = getActiveSegmentIndex();
|
|
|
|
|
const activeViewport = getActiveViewport();
|
|
|
|
|
|
|
|
|
|
if (segmentIndex === activeSegmentIndex) {
|
|
|
|
|
log.info(`${activeSegmentIndex} is already the active segment`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const labelmap3D = getActiveLabelMaps3D();
|
|
|
|
|
labelmap3D.activeSegmentIndex = segmentIndex;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Activates the correct label map if clicked segment
|
|
|
|
|
* does not belong to the active labelmap
|
|
|
|
|
*/
|
|
|
|
|
const { StudyInstanceUID } = activeViewport;
|
|
|
|
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
|
|
|
|
const allDisplaySets = studyMetadata.getDisplaySets();
|
|
|
|
|
let selectedSegmentation;
|
|
|
|
|
let newLabelmapIndex = getActiveLabelMapIndex();
|
|
|
|
|
allDisplaySets.forEach(displaySet => {
|
|
|
|
|
if (displaySet.labelmapSegments) {
|
|
|
|
|
Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
|
|
|
|
|
if (
|
|
|
|
|
displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)
|
|
|
|
|
) {
|
|
|
|
|
newLabelmapIndex = labelmapIndex;
|
|
|
|
|
selectedSegmentation =
|
|
|
|
|
displaySet.hasOverlapping === true
|
|
|
|
|
? displaySet.originLabelMapIndex
|
|
|
|
|
: labelmapIndex;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const brushStackState = getBrushStackState();
|
|
|
|
|
brushStackState.activeLabelmapIndex = newLabelmapIndex;
|
2021-04-20 16:28:38 +02:00
|
|
|
if (selectedSegmentation) {
|
|
|
|
|
setState(state => ({ ...state, selectedSegmentation }));
|
|
|
|
|
}
|
2021-01-16 01:19:12 -03:00
|
|
|
|
|
|
|
|
refreshViewports();
|
|
|
|
|
|
|
|
|
|
return segmentIndex;
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-13 16:45:26 -03:00
|
|
|
useEffect(() => {
|
|
|
|
|
const labelmapModifiedHandler = event => {
|
2020-04-01 00:54:02 -03:00
|
|
|
log.warn('Segmentation Panel: labelmap modified', event);
|
2021-01-16 01:19:12 -03:00
|
|
|
refreshSegmentations();
|
2020-03-13 16:45:26 -03:00
|
|
|
};
|
|
|
|
|
|
2020-05-06 14:17:44 -03:00
|
|
|
/*
|
|
|
|
|
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
|
|
|
|
*
|
|
|
|
|
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
|
|
|
|
* or other components that could rely on loaded segmentations that
|
|
|
|
|
* the segments were loaded so that e.g. when the user opens the panel
|
|
|
|
|
* before the segments are fully loaded, the panel can subscribe to this custom event
|
|
|
|
|
* and update itself with the new segments.
|
|
|
|
|
*
|
|
|
|
|
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
|
|
|
|
* updated after the segments are loaded) that React its not aware of its changes
|
|
|
|
|
* because the module object its not passed in to the panel component as prop but accessed externally.
|
|
|
|
|
*
|
|
|
|
|
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
|
|
|
|
* allows us to easily watch the module or the segmentations loading process in any other component
|
|
|
|
|
* without subscribing to external events.
|
|
|
|
|
*/
|
2020-08-10 11:20:31 +02:00
|
|
|
document.addEventListener(
|
|
|
|
|
'extensiondicomsegmentationsegloaded',
|
|
|
|
|
refreshSegmentations
|
|
|
|
|
);
|
2021-04-16 20:14:04 +02:00
|
|
|
document.addEventListener(
|
2021-04-20 16:28:38 +02:00
|
|
|
'extensiondicomsegmentationsegselected',
|
|
|
|
|
updateSegmentationComboBox
|
2021-04-16 20:14:04 +02:00
|
|
|
);
|
|
|
|
|
|
2020-03-13 16:45:26 -03:00
|
|
|
/*
|
|
|
|
|
* These are specific to each element;
|
|
|
|
|
* Need to iterate cornerstone-tools tracked enabled elements?
|
|
|
|
|
* Then only care about the one tied to active viewport?
|
|
|
|
|
*/
|
|
|
|
|
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
|
|
|
|
enabledElement.addEventListener(
|
|
|
|
|
'cornerstonetoolslabelmapmodified',
|
|
|
|
|
labelmapModifiedHandler
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return () => {
|
2020-08-10 11:20:31 +02:00
|
|
|
document.removeEventListener(
|
|
|
|
|
'extensiondicomsegmentationsegloaded',
|
|
|
|
|
refreshSegmentations
|
|
|
|
|
);
|
2021-04-16 20:14:04 +02:00
|
|
|
document.removeEventListener(
|
2021-04-20 16:28:38 +02:00
|
|
|
'extensiondicomsegmentationsegselected',
|
|
|
|
|
updateSegmentationComboBox
|
2021-04-16 20:14:04 +02:00
|
|
|
);
|
2020-03-13 16:45:26 -03:00
|
|
|
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
|
|
|
|
enabledElement.removeEventListener(
|
|
|
|
|
'cornerstonetoolslabelmapmodified',
|
|
|
|
|
labelmapModifiedHandler
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
};
|
2021-07-28 22:15:05 -03:00
|
|
|
}, [
|
|
|
|
|
activeIndex,
|
|
|
|
|
updateSegmentationComboBox,
|
|
|
|
|
viewports,
|
|
|
|
|
]);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-07-28 22:15:05 -03:00
|
|
|
const updateSegmentationComboBox = e => {
|
2021-04-20 16:28:38 +02:00
|
|
|
const index = e.detail.activatedLabelmapIndex;
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
setState(state => ({ ...state, selectedSegmentation: index }));
|
|
|
|
|
} else {
|
|
|
|
|
cleanSegmentationComboBox();
|
|
|
|
|
}
|
2021-07-28 22:15:05 -03:00
|
|
|
};
|
2021-04-20 16:28:38 +02:00
|
|
|
|
2021-04-16 20:14:04 +02:00
|
|
|
const cleanSegmentationComboBox = () => {
|
|
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
|
|
|
|
segmentsHidden: [],
|
|
|
|
|
segmentNumbers: [],
|
|
|
|
|
labelMapList: [],
|
|
|
|
|
segmentList: [],
|
|
|
|
|
isDisabled: true,
|
2021-04-21 10:35:27 +02:00
|
|
|
selectedSegmentation: -1,
|
2021-04-16 20:14:04 +02:00
|
|
|
}));
|
2021-07-28 22:15:05 -03:00
|
|
|
};
|
2021-04-16 20:14:04 +02:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const refreshSegmentations = () => {
|
|
|
|
|
const activeViewport = getActiveViewport();
|
2020-06-15 12:41:36 -03:00
|
|
|
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
|
|
|
|
|
if (!isDisabled) {
|
2021-01-16 01:19:12 -03:00
|
|
|
const brushStackState = getBrushStackState();
|
2020-06-15 12:41:36 -03:00
|
|
|
if (brushStackState) {
|
2021-01-16 01:19:12 -03:00
|
|
|
const labelMapList = getLabelMapList();
|
|
|
|
|
const {
|
|
|
|
|
items: segmentList,
|
|
|
|
|
numbers: segmentNumbers,
|
|
|
|
|
segmentsHidden,
|
|
|
|
|
} = getSegmentList();
|
2020-06-15 12:41:36 -03:00
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
2021-01-16 01:19:12 -03:00
|
|
|
segmentsHidden,
|
|
|
|
|
segmentNumbers,
|
|
|
|
|
labelMapList,
|
2020-06-15 12:41:36 -03:00
|
|
|
segmentList,
|
2020-08-10 11:20:31 +02:00
|
|
|
isDisabled,
|
2020-06-15 12:41:36 -03:00
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
2021-01-16 01:19:12 -03:00
|
|
|
segmentsHidden: [],
|
|
|
|
|
segmentNumbers: [],
|
|
|
|
|
labelMapList: [],
|
2020-06-15 12:41:36 -03:00
|
|
|
segmentList: [],
|
2020-08-10 11:20:31 +02:00
|
|
|
isDisabled,
|
2020-06-15 12:41:36 -03:00
|
|
|
}));
|
|
|
|
|
}
|
2020-04-01 00:54:02 -03:00
|
|
|
}
|
2021-01-16 01:19:12 -03:00
|
|
|
};
|
2020-08-10 11:20:31 +02:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
refreshSegmentations();
|
2020-05-04 16:10:39 +02:00
|
|
|
}, [
|
|
|
|
|
viewports,
|
|
|
|
|
activeIndex,
|
2020-08-10 11:20:31 +02:00
|
|
|
isOpen,
|
|
|
|
|
state.selectedSegmentation,
|
|
|
|
|
activeContexts,
|
|
|
|
|
state.isLoading,
|
2020-05-04 16:10:39 +02:00
|
|
|
]);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2020-04-01 00:54:02 -03:00
|
|
|
/* Handle open/closed panel behaviour */
|
|
|
|
|
useEffect(() => {
|
2020-04-24 07:25:05 -03:00
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
2021-01-16 01:19:12 -03:00
|
|
|
showSettings: state.showSettings && !isOpen,
|
2020-04-24 07:25:05 -03:00
|
|
|
}));
|
2020-04-01 00:54:02 -03:00
|
|
|
}, [isOpen]);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const getLabelMapList = () => {
|
|
|
|
|
const activeViewport = getActiveViewport();
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
/* Get list of SEG labelmaps specific to active viewport (reference series) */
|
|
|
|
|
const referencedSegDisplaysets = _getReferencedSegDisplaysets(
|
|
|
|
|
activeViewport.StudyInstanceUID,
|
|
|
|
|
activeViewport.SeriesInstanceUID
|
|
|
|
|
);
|
2020-04-24 07:25:05 -03:00
|
|
|
|
2021-04-16 20:14:04 +02:00
|
|
|
const filteredReferencedSegDisplaysets = referencedSegDisplaysets.filter(
|
2021-07-28 22:15:05 -03:00
|
|
|
segDisplay => segDisplay.loadError !== true
|
|
|
|
|
);
|
2021-04-16 20:14:04 +02:00
|
|
|
|
|
|
|
|
return filteredReferencedSegDisplaysets.map((displaySet, index) => {
|
2021-01-16 01:19:12 -03:00
|
|
|
const {
|
|
|
|
|
labelmapIndex,
|
|
|
|
|
originLabelMapIndex,
|
|
|
|
|
hasOverlapping,
|
|
|
|
|
SeriesDate,
|
|
|
|
|
SeriesTime,
|
|
|
|
|
} = displaySet;
|
|
|
|
|
|
|
|
|
|
/* Map to display representation */
|
|
|
|
|
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
|
|
|
|
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
2021-03-31 12:20:34 +02:00
|
|
|
const displayDate = date.format('ddd, MMM Do YYYY, h:mm:ss a');
|
2021-01-16 01:19:12 -03:00
|
|
|
const displayDescription = displaySet.SeriesDescription;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
value: hasOverlapping === true ? originLabelMapIndex : labelmapIndex,
|
|
|
|
|
title: displayDescription,
|
|
|
|
|
description: displayDate,
|
|
|
|
|
onClick: async () => {
|
|
|
|
|
const activatedLabelmapIndex = await setActiveLabelmap(
|
|
|
|
|
activeViewport,
|
|
|
|
|
studies,
|
|
|
|
|
displaySet,
|
|
|
|
|
onSelectedSegmentationChange,
|
|
|
|
|
onDisplaySetLoadFailure
|
|
|
|
|
);
|
|
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
|
|
|
|
selectedSegmentation: activatedLabelmapIndex,
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const setCurrentSelectedSegment = segmentNumber => {
|
|
|
|
|
setActiveSegment(segmentNumber);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const sameSegment = state.selectedSegment === segmentNumber;
|
|
|
|
|
if (!sameSegment) {
|
|
|
|
|
setState(state => ({ ...state, selectedSegment: segmentNumber }));
|
|
|
|
|
}
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const validIndexList = [];
|
|
|
|
|
getActiveLabelMaps2D().forEach((labelMap2D, index) => {
|
|
|
|
|
if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
|
|
|
|
|
validIndexList.push(index);
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const avg = array => array.reduce((a, b) => a + b) / array.length;
|
|
|
|
|
const average = avg(validIndexList);
|
|
|
|
|
const closest = validIndexList.reduce((prev, curr) => {
|
|
|
|
|
return Math.abs(curr - average) < Math.abs(prev - average) ? curr : prev;
|
|
|
|
|
});
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
if (isCornerstone()) {
|
|
|
|
|
const element = getEnabledElement();
|
|
|
|
|
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
if (!toolState) return;
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const imageIds = toolState.data[0].imageIds;
|
|
|
|
|
const imageId = imageIds[closest];
|
|
|
|
|
const frameIndex = imageIds.indexOf(imageId);
|
2020-12-10 11:04:27 +01:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const SOPInstanceUID = cornerstone.metaData.get(
|
|
|
|
|
'SOPInstanceUID',
|
|
|
|
|
imageId
|
|
|
|
|
);
|
|
|
|
|
const StudyInstanceUID = cornerstone.metaData.get(
|
|
|
|
|
'StudyInstanceUID',
|
|
|
|
|
imageId
|
|
|
|
|
);
|
2020-04-24 07:25:05 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
DICOMSegTempCrosshairsTool.addCrosshair(element, imageId, segmentNumber);
|
2020-05-06 14:17:44 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
onSegmentItemClick({
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
SOPInstanceUID,
|
|
|
|
|
frameIndex,
|
|
|
|
|
activeViewportIndex: activeIndex,
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-04-24 07:25:05 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
if (isVTK()) {
|
|
|
|
|
const labelMaps3D = getActiveLabelMaps3D();
|
|
|
|
|
const currentDisplaySet = getCurrentDisplaySet();
|
|
|
|
|
const frame = labelMaps3D.labelmaps2D[closest];
|
|
|
|
|
|
|
|
|
|
onSegmentItemClick({
|
|
|
|
|
studies,
|
|
|
|
|
StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
|
|
|
|
|
displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
|
|
|
|
|
SOPClassUID: getActiveViewport().sopClassUIDs[0],
|
|
|
|
|
SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
|
|
|
|
|
segmentNumber,
|
|
|
|
|
frameIndex: closest,
|
|
|
|
|
frame,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-04-24 07:25:05 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const getColorLUTTable = () => {
|
|
|
|
|
const { state } = cornerstoneTools.getModule('segmentation');
|
|
|
|
|
const { colorLUTIndex } = getActiveLabelMaps3D();
|
|
|
|
|
return state.colorLutTables[colorLUTIndex];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getEnabledElement = () => {
|
|
|
|
|
const enabledElements = cornerstone.getEnabledElements();
|
|
|
|
|
return enabledElements[activeIndex].element;
|
|
|
|
|
};
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-10-26 13:00:01 +02:00
|
|
|
const onSegmentVisibilityChangeHandler = (
|
|
|
|
|
isVisible,
|
|
|
|
|
segmentNumber,
|
|
|
|
|
labelmap3D
|
|
|
|
|
) => {
|
|
|
|
|
let segmentsHidden = [];
|
|
|
|
|
if (labelmap3D.metadata.hasOverlapping) {
|
|
|
|
|
/** Get all labelmaps with this segmentNumber and that
|
|
|
|
|
* are from the same series (overlapping segments) */
|
|
|
|
|
const { labelmaps3D } = getBrushStackState();
|
|
|
|
|
|
|
|
|
|
const sameSeriesLabelMaps3D = labelmaps3D.filter(({ metadata }) => {
|
|
|
|
|
return (
|
|
|
|
|
labelmap3D.metadata.segmentationSeriesInstanceUID ===
|
|
|
|
|
metadata.segmentationSeriesInstanceUID
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const possibleLabelMaps3D = sameSeriesLabelMaps3D.filter(
|
|
|
|
|
({ labelmaps2D }) => {
|
|
|
|
|
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
|
|
|
|
|
segmentsOnLabelmap.includes(segmentNumber)
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-01-19 14:10:40 -03:00
|
|
|
);
|
2020-04-01 00:54:02 -03:00
|
|
|
|
2021-10-26 13:00:01 +02:00
|
|
|
possibleLabelMaps3D.forEach(labelmap3D => {
|
|
|
|
|
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
2020-05-06 14:17:44 -03:00
|
|
|
|
2021-10-26 13:00:01 +02:00
|
|
|
segmentsHidden = [
|
|
|
|
|
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
|
|
|
|
segmentsHidden = [...labelmap3D.segmentsHidden];
|
|
|
|
|
}
|
2021-01-19 14:10:40 -03:00
|
|
|
|
|
|
|
|
setState(state => ({ ...state, segmentsHidden }));
|
2020-05-06 14:17:44 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
refreshSegmentations();
|
|
|
|
|
refreshViewports();
|
2021-01-19 14:22:52 -03:00
|
|
|
|
|
|
|
|
if (isVTK()) {
|
|
|
|
|
onSegmentVisibilityChange(segmentNumber, isVisible);
|
|
|
|
|
}
|
2020-05-06 14:17:44 -03:00
|
|
|
};
|
|
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const getSegmentList = () => {
|
|
|
|
|
/*
|
|
|
|
|
* Newly created segments have no `meta`
|
|
|
|
|
* So we instead build a list of all segment indexes in use
|
|
|
|
|
* Then find any associated metadata
|
|
|
|
|
*/
|
|
|
|
|
const uniqueSegmentIndexes = getActiveLabelMaps2D()
|
|
|
|
|
.reduce((acc, labelmap2D) => {
|
|
|
|
|
if (labelmap2D) {
|
|
|
|
|
const segmentIndexes = labelmap2D.segmentsOnLabelmap;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < segmentIndexes.length; i++) {
|
|
|
|
|
if (!acc.includes(segmentIndexes[i]) && segmentIndexes[i] !== 0) {
|
|
|
|
|
acc.push(segmentIndexes[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-06 14:17:44 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
return acc;
|
|
|
|
|
}, [])
|
|
|
|
|
.sort((a, b) => a - b);
|
|
|
|
|
|
|
|
|
|
const labelmap3D = getActiveLabelMaps3D();
|
|
|
|
|
const colorLutTable = getColorLUTTable();
|
|
|
|
|
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
|
|
|
|
|
|
|
|
|
|
const segmentList = [];
|
|
|
|
|
const segmentNumbers = [];
|
|
|
|
|
for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
|
|
|
|
|
const segmentIndex = uniqueSegmentIndexes[i];
|
|
|
|
|
|
|
|
|
|
const color = colorLutTable[segmentIndex];
|
|
|
|
|
let segmentLabel = '(unlabeled)';
|
|
|
|
|
let segmentNumber = segmentIndex;
|
|
|
|
|
|
|
|
|
|
/* Meta */
|
|
|
|
|
if (hasLabelmapMeta) {
|
|
|
|
|
const segmentMeta = labelmap3D.metadata.data[segmentIndex];
|
|
|
|
|
if (segmentMeta) {
|
|
|
|
|
segmentNumber = segmentMeta.SegmentNumber;
|
|
|
|
|
segmentLabel = segmentMeta.SegmentLabel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sameSegment = state.selectedSegment === segmentNumber;
|
|
|
|
|
|
|
|
|
|
segmentNumbers.push(segmentNumber);
|
|
|
|
|
segmentList.push(
|
|
|
|
|
<SegmentItem
|
|
|
|
|
key={segmentNumber}
|
|
|
|
|
itemClass={`segment-item ${sameSegment && 'selected'}`}
|
|
|
|
|
onClick={setCurrentSelectedSegment}
|
|
|
|
|
label={segmentLabel}
|
|
|
|
|
index={segmentNumber}
|
|
|
|
|
color={color}
|
2021-10-26 13:00:01 +02:00
|
|
|
labelmap3D={labelmap3D}
|
2021-01-16 01:19:12 -03:00
|
|
|
visible={!labelmap3D.segmentsHidden[segmentIndex]}
|
|
|
|
|
onVisibilityChange={onSegmentVisibilityChangeHandler}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
items: segmentList,
|
|
|
|
|
numbers: segmentNumbers,
|
|
|
|
|
segmentsHidden: labelmap3D.segmentsHidden,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Let's iterate over segmentIndexes ^ above
|
|
|
|
|
* If meta has a match, use it to show info
|
|
|
|
|
* If now, add "no-meta" class
|
|
|
|
|
* Show default name
|
|
|
|
|
*/
|
2020-04-01 00:54:02 -03:00
|
|
|
};
|
2020-03-13 16:45:26 -03:00
|
|
|
|
|
|
|
|
const updateBrushSize = evt => {
|
|
|
|
|
const updatedRadius = Number(evt.target.value);
|
|
|
|
|
|
|
|
|
|
if (updatedRadius !== brushRadius) {
|
2021-01-16 01:19:12 -03:00
|
|
|
setState(state => ({ ...state, brushRadius: updatedRadius }));
|
2020-04-01 00:54:02 -03:00
|
|
|
const module = cornerstoneTools.getModule('segmentation');
|
|
|
|
|
module.setters.radius(updatedRadius);
|
2020-03-13 16:45:26 -03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const decrementSegment = event => {
|
2021-01-16 01:19:12 -03:00
|
|
|
const activeSegmentIndex = getActiveSegmentIndex();
|
2020-03-13 16:45:26 -03:00
|
|
|
event.preventDefault();
|
2021-01-16 01:19:12 -03:00
|
|
|
if (activeSegmentIndex > 1) {
|
|
|
|
|
activeSegmentIndex--;
|
2020-03-13 16:45:26 -03:00
|
|
|
}
|
2021-01-16 01:19:12 -03:00
|
|
|
setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
|
2020-03-13 16:45:26 -03:00
|
|
|
updateActiveSegmentColor();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const incrementSegment = event => {
|
2021-01-16 01:19:12 -03:00
|
|
|
const activeSegmentIndex = getActiveSegmentIndex();
|
2020-03-13 16:45:26 -03:00
|
|
|
event.preventDefault();
|
2021-01-16 01:19:12 -03:00
|
|
|
activeSegmentIndex++;
|
|
|
|
|
setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
|
2020-03-13 16:45:26 -03:00
|
|
|
updateActiveSegmentColor();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateActiveSegmentColor = () => {
|
|
|
|
|
const color = getActiveSegmentColor();
|
2021-01-16 01:19:12 -03:00
|
|
|
setState(state => ({ ...state, brushColor: color }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getBrushStackState = () => {
|
|
|
|
|
const module = cornerstoneTools.getModule('segmentation');
|
|
|
|
|
const firstImageId = getFirstImageId();
|
|
|
|
|
const brushStackState = module.state.series[firstImageId];
|
|
|
|
|
return brushStackState;
|
2020-03-13 16:45:26 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveSegmentColor = () => {
|
2021-01-16 01:19:12 -03:00
|
|
|
const brushStackState = getBrushStackState();
|
|
|
|
|
if (!brushStackState) {
|
2020-03-13 16:45:26 -03:00
|
|
|
return 'rgba(255, 255, 255, 1)';
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const colorLutTable = getColorLUTTable();
|
2020-03-13 16:45:26 -03:00
|
|
|
const color = colorLutTable[labelmap3D.activeSegmentIndex];
|
|
|
|
|
return `rgba(${color.join(',')})`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateConfiguration = newConfiguration => {
|
|
|
|
|
configuration.renderFill = newConfiguration.renderFill;
|
|
|
|
|
configuration.renderOutline = newConfiguration.renderOutline;
|
|
|
|
|
configuration.shouldRenderInactiveLabelmaps =
|
|
|
|
|
newConfiguration.shouldRenderInactiveLabelmaps;
|
|
|
|
|
configuration.fillAlpha = newConfiguration.fillAlpha;
|
|
|
|
|
configuration.outlineAlpha = newConfiguration.outlineAlpha;
|
|
|
|
|
configuration.outlineWidth = newConfiguration.outlineWidth;
|
|
|
|
|
configuration.fillAlphaInactive = newConfiguration.fillAlphaInactive;
|
|
|
|
|
configuration.outlineAlphaInactive = newConfiguration.outlineAlphaInactive;
|
2020-05-06 14:17:44 -03:00
|
|
|
onConfigurationChange(newConfiguration);
|
2020-08-10 11:20:31 +02:00
|
|
|
refreshViewports();
|
2020-03-13 16:45:26 -03:00
|
|
|
};
|
|
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const onVisibilityChangeHandler = isVisible => {
|
2021-01-19 14:10:40 -03:00
|
|
|
let segmentsHidden = [];
|
2021-01-16 01:19:12 -03:00
|
|
|
state.segmentNumbers.forEach(segmentNumber => {
|
2021-01-19 14:22:52 -03:00
|
|
|
if (isVTK()) {
|
|
|
|
|
onSegmentVisibilityChange(segmentNumber, isVisible);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-19 14:10:40 -03:00
|
|
|
/** Get all labelmaps with this segmentNumber (overlapping segments) */
|
|
|
|
|
const { labelmaps3D } = getBrushStackState();
|
|
|
|
|
const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
|
|
|
|
|
return labelmaps2D.some(({ segmentsOnLabelmap }) =>
|
|
|
|
|
segmentsOnLabelmap.includes(segmentNumber)
|
|
|
|
|
);
|
|
|
|
|
});
|
2021-01-16 01:19:12 -03:00
|
|
|
|
2021-01-19 14:10:40 -03:00
|
|
|
possibleLabelMaps3D.forEach(labelmap3D => {
|
2021-01-19 14:22:52 -03:00
|
|
|
labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
|
2021-01-19 14:10:40 -03:00
|
|
|
segmentsHidden = [
|
|
|
|
|
...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
|
|
|
|
|
];
|
|
|
|
|
});
|
2021-01-16 01:19:12 -03:00
|
|
|
});
|
|
|
|
|
|
2021-01-19 14:10:40 -03:00
|
|
|
setState(state => ({ ...state, segmentsHidden }));
|
2021-01-16 01:19:12 -03:00
|
|
|
|
|
|
|
|
refreshSegmentations();
|
|
|
|
|
refreshViewports();
|
|
|
|
|
};
|
|
|
|
|
|
2020-08-10 11:20:31 +02:00
|
|
|
const disabledConfigurationFields = [
|
|
|
|
|
'outlineAlpha',
|
|
|
|
|
'shouldRenderInactiveLabelmaps',
|
|
|
|
|
];
|
2021-01-16 01:19:12 -03:00
|
|
|
|
|
|
|
|
const selectedSegmentationOption = state.labelMapList.find(
|
|
|
|
|
i => i.value === state.selectedSegmentation
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (state.showSettings) {
|
2020-03-13 16:45:26 -03:00
|
|
|
return (
|
|
|
|
|
<SegmentationSettings
|
2020-05-06 14:17:44 -03:00
|
|
|
disabledFields={isVTK() ? disabledConfigurationFields : []}
|
2020-03-13 16:45:26 -03:00
|
|
|
configuration={configuration}
|
2021-01-16 01:19:12 -03:00
|
|
|
onBack={() => setState(state => ({ ...state, showSettings: false }))}
|
2020-03-13 16:45:26 -03:00
|
|
|
onChange={updateConfiguration}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return (
|
2020-08-10 11:20:31 +02:00
|
|
|
<div
|
2021-01-16 01:19:12 -03:00
|
|
|
className={classNames('dcmseg-segmentation-panel', {
|
|
|
|
|
disabled: state.isDisabled,
|
|
|
|
|
})}
|
2020-08-10 11:20:31 +02:00
|
|
|
>
|
2020-03-13 16:45:26 -03:00
|
|
|
{false && (
|
|
|
|
|
<form className="selector-form">
|
|
|
|
|
<BrushColorSelector
|
2021-01-16 01:19:12 -03:00
|
|
|
defaultColor={state.brushColor}
|
2020-04-01 00:54:02 -03:00
|
|
|
index={state.selectedSegment}
|
2020-03-13 16:45:26 -03:00
|
|
|
onNext={incrementSegment}
|
|
|
|
|
onPrev={decrementSegment}
|
|
|
|
|
/>
|
|
|
|
|
<BrushRadius
|
2021-01-16 01:19:12 -03:00
|
|
|
value={state.brushRadius}
|
2020-03-13 16:45:26 -03:00
|
|
|
onChange={updateBrushSize}
|
|
|
|
|
min={configuration.minRadius}
|
|
|
|
|
max={configuration.maxRadius}
|
|
|
|
|
/>
|
|
|
|
|
</form>
|
|
|
|
|
)}
|
2021-01-16 01:19:12 -03:00
|
|
|
<Icon
|
|
|
|
|
className="cog-icon"
|
|
|
|
|
name="cog"
|
|
|
|
|
width="25px"
|
|
|
|
|
height="25px"
|
|
|
|
|
onClick={() => setState(state => ({ ...state, showSettings: true }))}
|
|
|
|
|
/>
|
2020-03-13 16:45:26 -03:00
|
|
|
<h3>Segmentations</h3>
|
|
|
|
|
<div className="segmentations">
|
|
|
|
|
<SegmentationSelect
|
2021-01-16 01:19:12 -03:00
|
|
|
value={selectedSegmentationOption}
|
2020-03-13 16:45:26 -03:00
|
|
|
formatOptionLabel={SegmentationItem}
|
2021-01-16 01:19:12 -03:00
|
|
|
options={state.labelMapList}
|
2020-03-13 16:45:26 -03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2021-01-16 01:19:12 -03:00
|
|
|
<SegmentsSection
|
|
|
|
|
count={state.segmentList.length}
|
|
|
|
|
isVisible={
|
|
|
|
|
state.segmentsHidden.filter(isHidden => isHidden === true).length <
|
|
|
|
|
state.segmentNumbers.length
|
|
|
|
|
}
|
|
|
|
|
onVisibilityChange={onVisibilityChangeHandler}
|
|
|
|
|
>
|
|
|
|
|
<ScrollableArea>
|
|
|
|
|
<TableList headless>{state.segmentList}</TableList>
|
|
|
|
|
</ScrollableArea>
|
|
|
|
|
</SegmentsSection>
|
2020-03-13 16:45:26 -03:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SegmentationPanel.propTypes = {
|
|
|
|
|
/*
|
|
|
|
|
* An object, with int index keys?
|
|
|
|
|
* Maps to: state.viewports.viewportSpecificData, in `viewer`
|
|
|
|
|
* Passed in MODULE_TYPES.PANEL when specifying component in viewer
|
|
|
|
|
*/
|
|
|
|
|
viewports: PropTypes.shape({
|
|
|
|
|
displaySetInstanceUID: PropTypes.string,
|
|
|
|
|
frameRate: PropTypes.any,
|
|
|
|
|
InstanceNumber: PropTypes.number,
|
|
|
|
|
isMultiFrame: PropTypes.bool,
|
|
|
|
|
isReconstructable: PropTypes.bool,
|
|
|
|
|
Modality: PropTypes.string,
|
|
|
|
|
plugin: PropTypes.string,
|
|
|
|
|
SeriesDate: PropTypes.string,
|
|
|
|
|
SeriesDescription: PropTypes.string,
|
|
|
|
|
SeriesInstanceUID: PropTypes.string,
|
|
|
|
|
SeriesNumber: PropTypes.any,
|
|
|
|
|
SeriesTime: PropTypes.string,
|
|
|
|
|
sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
|
|
|
|
|
StudyInstanceUID: PropTypes.string,
|
|
|
|
|
}),
|
|
|
|
|
activeIndex: PropTypes.number.isRequired,
|
|
|
|
|
studies: PropTypes.array.isRequired,
|
2020-04-09 07:21:43 -03:00
|
|
|
isOpen: PropTypes.bool.isRequired,
|
2020-03-13 16:45:26 -03:00
|
|
|
};
|
|
|
|
|
SegmentationPanel.defaultProps = {};
|
|
|
|
|
|
|
|
|
|
/**
|
2021-01-16 01:19:12 -03:00
|
|
|
* Returns SEG DisplaySets that reference the target series, sorted by dateTime
|
2020-03-13 16:45:26 -03:00
|
|
|
*
|
|
|
|
|
* @param {string} StudyInstanceUID
|
|
|
|
|
* @param {string} SeriesInstanceUID
|
|
|
|
|
* @returns Array
|
|
|
|
|
*/
|
|
|
|
|
const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
|
|
|
|
|
/* Referenced DisplaySets */
|
|
|
|
|
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
|
|
|
|
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
|
|
|
|
referencedSeriesInstanceUID: SeriesInstanceUID,
|
|
|
|
|
Modality: 'SEG',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Sort */
|
|
|
|
|
referencedDisplaysets.sort((a, b) => {
|
|
|
|
|
const aNumber = Number(`${a.SeriesDate}${a.SeriesTime}`);
|
|
|
|
|
const bNumber = Number(`${b.SeriesDate}${b.SeriesTime}`);
|
2021-03-31 12:20:34 +02:00
|
|
|
return bNumber - aNumber;
|
2020-03-13 16:45:26 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return referencedDisplaysets;
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const SegmentsSection = ({
|
|
|
|
|
count,
|
|
|
|
|
children,
|
|
|
|
|
isVisible: defaultVisibility,
|
|
|
|
|
onVisibilityChange,
|
|
|
|
|
}) => {
|
|
|
|
|
const [isVisible, setIsVisible] = useState(defaultVisibility);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const onVisibilityChangeHandler = () => {
|
|
|
|
|
const newVisibility = !isVisible;
|
|
|
|
|
setIsVisible(newVisibility);
|
|
|
|
|
onVisibilityChange(newVisibility);
|
|
|
|
|
};
|
2020-12-10 11:04:27 +01:00
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
useEffect(() => {
|
|
|
|
|
setIsVisible(defaultVisibility);
|
|
|
|
|
}, [defaultVisibility]);
|
2020-03-13 16:45:26 -03:00
|
|
|
|
|
|
|
|
return (
|
2021-01-16 01:19:12 -03:00
|
|
|
<div className="SegmentsSection">
|
|
|
|
|
<div className="header">
|
|
|
|
|
<div>Segments</div>
|
|
|
|
|
<div className="icons">
|
|
|
|
|
<Icon
|
|
|
|
|
className={`eye-icon ${isVisible && 'expanded'}`}
|
|
|
|
|
name={isVisible ? 'eye' : 'eye-closed'}
|
|
|
|
|
width="20px"
|
|
|
|
|
height="20px"
|
|
|
|
|
onClick={onVisibilityChangeHandler}
|
|
|
|
|
/>
|
|
|
|
|
<div className="count">{count}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
2020-03-13 16:45:26 -03:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-16 01:19:12 -03:00
|
|
|
const noop = () => {};
|
|
|
|
|
|
|
|
|
|
SegmentsSection.defaultProps = {
|
|
|
|
|
onVisibilityChange: noop,
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-13 16:45:26 -03:00
|
|
|
export default SegmentationPanel;
|