}
itemMetaClass="segment-color-section"
- onItemClick={onClick}
+ onItemClick={onClickHandler}
>
@@ -63,12 +72,7 @@ const SegmentItem = ({
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
- onClick={event => {
- event.stopPropagation();
- const newVisibility = !isVisible;
- setIsVisible(newVisibility);
- onVisibilityChange(newVisibility);
- }}
+ onClick={onVisibilityChangeHandler}
/>
{false &&
{'...'}
}
diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.css b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.css
index d22eafa19..6f22c392d 100644
--- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.css
+++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.css
@@ -6,6 +6,7 @@
width: 100%;
display: flex;
flex-direction: column;
+ padding: 20px;
}
.dcmseg-segmentation-panel.disabled {
@@ -14,19 +15,19 @@
}
.dcmseg-segmentation-panel h3 {
- padding-left: 20px;
margin-top: 0;
}
.dcmseg-segmentation-panel .segmentations {
- padding: 0 20px 20px 20px;
+ padding-bottom: 10px;
}
.dcmseg-segmentation-panel .cog-icon {
- margin: 20px 20px 0 0;
align-self: flex-end;
color: var(--default-color);
cursor: pointer;
+ min-height: 23px;
+ max-height: 23px;
}
.dcmseg-segmentation-panel .cog-icon:hover {
@@ -48,11 +49,11 @@
height: 150px;
}
-.dcmseg-segmentation-panel .selector-form>div {
+.dcmseg-segmentation-panel .selector-form > div {
width: 100%;
}
-.dcmseg-segmentation-panel .selector-form>div:first-of-type {
+.dcmseg-segmentation-panel .selector-form > div:first-of-type {
display: flex;
flex-direction: row;
justify-content: center;
@@ -64,3 +65,61 @@
flex: unset;
margin-right: 16px;
}
+
+.SegmentsSection {
+ display: flex;
+ flex-direction: column;
+ color: var(--text-primary-color);
+ font-size: 12px;
+ margin-bottom: 0px;
+ height: 100%;
+}
+
+.SegmentsSection .header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ background-color: var(--ui-gray-dark);
+ border-radius: 5px;
+ font-weight: bold;
+ padding: 10px 15px;
+}
+
+.SegmentsSection .count {
+ font-size: 15px;
+ color: var(--active-color);
+ margin-left: 15px;
+ border-radius: 5px;
+ background-color: var(--ui-gray-darker);
+ text-align: center;
+ min-width: 25px;
+}
+
+.SegmentsSection .header .eye-icon,
+.SegmentsSection .header .angle-double-down,
+.SegmentsSection .header .angle-double-up {
+ cursor: pointer;
+ color: var(--active-color);
+}
+
+.SegmentsSection .header .eye-icon:hover,
+.SegmentsSection .header .angle-double-down:hover,
+.SegmentsSection .header .angle-double-up:hover {
+ color: var(--hover-color);
+}
+
+.SegmentsSection .header .eye-icon.expanded {
+ color: var(--default-color);
+}
+
+.SegmentsSection .header .icons {
+ display: flex;
+ align-items: center;
+ border-radius: 5px;
+}
+
+.SegmentsSection .header .icons .angle-double-down,
+.SegmentsSection .header .icons .angle-double-up {
+ margin-left: 15px;
+}
diff --git a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js
index 1cd292442..8bd61e3b7 100644
--- a/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js
+++ b/extensions/dicom-segmentation/src/components/SegmentationPanel/SegmentationPanel.js
@@ -1,8 +1,9 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import moment from 'moment';
+import classNames from 'classnames';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
@@ -67,29 +68,104 @@ const SegmentationPanel = ({
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)',
- selectedSegment: null,
- selectedSegmentation: null,
- showSegmentationSettings: false,
- brushStackState: null,
- labelmapList: [],
+ selectedSegment: 0,
+ selectedSegmentation: 0,
+ showSettings: false,
+ labelMapList: [],
segmentList: [],
- cachedSegmentsProperties: [],
+ segmentsHidden: [],
+ segmentNumbers: [],
isLoading: false,
isDisabled: true,
});
+ 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;
+ setState(state => ({ ...state, selectedSegmentation }));
+
+ refreshViewports();
+
+ return segmentIndex;
+ };
+
useEffect(() => {
const labelmapModifiedHandler = event => {
log.warn('Segmentation Panel: labelmap modified', event);
- const module = cornerstoneTools.getModule('segmentation');
- const activeViewport = viewports[activeIndex];
- const studyMetadata = studyMetadataManager.get(
- activeViewport.StudyInstanceUID
- );
- const firstImageId = studyMetadata.getFirstImageId(
- activeViewport.displaySetInstanceUID
- );
- updateState('brushStackState', module.state.series[firstImageId]);
+ refreshSegmentations();
};
/*
@@ -140,50 +216,38 @@ const SegmentationPanel = ({
};
}, [activeIndex, viewports]);
- const refreshSegmentations = useCallback(() => {
- const module = cornerstoneTools.getModule('segmentation');
- const activeViewport = viewports[activeIndex];
-
+ const refreshSegmentations = () => {
+ const activeViewport = getActiveViewport();
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
if (!isDisabled) {
- const studyMetadata = studyMetadataManager.get(
- activeViewport.StudyInstanceUID
- );
- const firstImageId = studyMetadata.getFirstImageId(
- activeViewport.displaySetInstanceUID
- );
- const brushStackState = module.state.series[firstImageId];
+ const brushStackState = getBrushStackState();
if (brushStackState) {
- const labelmap3D =
- brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
- const labelmapList = getLabelmapList(
- brushStackState,
- firstImageId,
- activeViewport,
- );
- const segmentList = getSegmentList(
- labelmap3D,
- firstImageId,
- brushStackState,
- );
+ const labelMapList = getLabelMapList();
+ const {
+ items: segmentList,
+ numbers: segmentNumbers,
+ segmentsHidden,
+ } = getSegmentList();
setState(state => ({
...state,
- brushStackState,
- selectedSegmentation: brushStackState.activeLabelmapIndex,
- labelmapList,
+ segmentsHidden,
+ segmentNumbers,
+ labelMapList,
segmentList,
isDisabled,
}));
} else {
setState(state => ({
...state,
- labelmapList: [],
+ segmentsHidden: [],
+ segmentNumbers: [],
+ labelMapList: [],
segmentList: [],
isDisabled,
}));
}
}
- }, [viewports, activeIndex, state.isLoading]);
+ };
useEffect(() => {
refreshSegmentations();
@@ -200,309 +264,285 @@ const SegmentationPanel = ({
useEffect(() => {
setState(state => ({
...state,
- showSegmentationSettings: state.showSegmentationSettings && !isOpen,
+ showSettings: state.showSettings && !isOpen,
}));
}, [isOpen]);
- const getLabelmapList = useCallback(
- (brushStackState, firstImageId, activeViewport) => {
- /* Get list of SEG labelmaps specific to active viewport (reference series) */
- const referencedSegDisplaysets = _getReferencedSegDisplaysets(
- activeViewport.StudyInstanceUID,
- activeViewport.SeriesInstanceUID
- );
+ const getLabelMapList = () => {
+ const activeViewport = getActiveViewport();
- return referencedSegDisplaysets.map((displaySet, index) => {
- const { labelmapIndex, SeriesDate, SeriesTime } = displaySet;
+ /* Get list of SEG labelmaps specific to active viewport (reference series) */
+ const referencedSegDisplaysets = _getReferencedSegDisplaysets(
+ activeViewport.StudyInstanceUID,
+ activeViewport.SeriesInstanceUID
+ );
- /* Map to display representation */
- const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
- const date = moment(dateStr, 'YYYYMMDD:HHmmss');
- const isActiveLabelmap =
- labelmapIndex === brushStackState.activeLabelmapIndex;
- const displayDate = date.format('ddd, MMM Do YYYY');
- const displayTime = date.format('h:mm:ss a');
- const displayDescription = displaySet.SeriesDescription;
+ return referencedSegDisplaysets.map((displaySet, index) => {
+ const {
+ labelmapIndex,
+ originLabelMapIndex,
+ hasOverlapping,
+ SeriesDate,
+ SeriesTime,
+ } = displaySet;
- return {
- value: labelmapIndex,
- title: displayDescription,
- description: displayDate,
- onClick: async () => {
- const activatedLabelmapIndex = await setActiveLabelmap(
- activeViewport,
- studies,
- displaySet,
- () => onSelectedSegmentationChange(),
- onDisplaySetLoadFailure
- );
- updateState('selectedSegmentation', activatedLabelmapIndex);
- },
- };
- });
- },
- [studies]
- );
+ /* Map to display representation */
+ const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
+ const date = moment(dateStr, 'YYYYMMDD:HHmmss');
+ const displayDate = date.format('ddd, MMM Do YYYY');
+ const displayDescription = displaySet.SeriesDescription;
- const getSegmentList = useCallback(
- (labelmap3D, firstImageId, brushStackState) => {
- /*
- * 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 = labelmap3D.labelmaps2D
- .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]);
- }
- }
- }
-
- return acc;
- }, [])
- .sort((a, b) => a - b);
-
- const module = cornerstoneTools.getModule('segmentation');
- const colorLutTable =
- module.state.colorLutTables[labelmap3D.colorLUTIndex];
- const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
-
- const segmentList = [];
- 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;
- const setCurrentSelectedSegment = () => {
- const activeViewport = viewports[activeIndex];
-
- _setActiveSegment(
- firstImageId,
- segmentNumber,
- labelmap3D.activeSegmentIndex,
- activeViewport
+ return {
+ value: hasOverlapping === true ? originLabelMapIndex : labelmapIndex,
+ title: displayDescription,
+ description: displayDate,
+ onClick: async () => {
+ const activatedLabelmapIndex = await setActiveLabelmap(
+ activeViewport,
+ studies,
+ displaySet,
+ onSelectedSegmentationChange,
+ onDisplaySetLoadFailure
);
- updateState('selectedSegment', sameSegment ? null : segmentNumber);
-
- const validIndexList = [];
- labelmap3D.labelmaps2D.forEach((labelMap2D, index) => {
- if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
- validIndexList.push(index);
- }
- });
- 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;
- });
-
- if (isCornerstone()) {
- const enabledElements = cornerstone.getEnabledElements();
- const element = enabledElements[activeIndex].element;
- const toolState = cornerstoneTools.getToolState(element, 'stack');
-
- if (!toolState) {
- return;
- }
-
- const imageIds = toolState.data[0].imageIds;
- const imageId = imageIds[closest];
- const frameIndex = imageIds.indexOf(imageId);
-
- const SOPInstanceUID = cornerstone.metaData.get(
- 'SOPInstanceUID',
- imageId
- );
- const StudyInstanceUID = cornerstone.metaData.get(
- 'StudyInstanceUID',
- imageId
- );
-
- DICOMSegTempCrosshairsTool.addCrosshair(
- element,
- imageId,
- segmentNumber
- );
-
- onSegmentItemClick({
- StudyInstanceUID,
- SOPInstanceUID,
- frameIndex,
- activeViewportIndex: activeIndex,
- });
- }
-
- if (isVTK()) {
- const activeViewport = viewports[activeIndex];
- const studyMetadata = studyMetadataManager.get(
- activeViewport.StudyInstanceUID
- );
- const allDisplaySets = studyMetadata.getDisplaySets();
- const currentDisplaySet = allDisplaySets.find(
- displaySet =>
- displaySet.displaySetInstanceUID ===
- activeViewport.displaySetInstanceUID
- );
-
- const frame = labelmap3D.labelmaps2D[closest];
-
- onSegmentItemClick({
- studies,
- StudyInstanceUID: currentDisplaySet.StudyInstanceUID,
- displaySetInstanceUID: currentDisplaySet.displaySetInstanceUID,
- SOPClassUID: viewports[activeIndex].sopClassUIDs[0],
- SOPInstanceUID: currentDisplaySet.SOPInstanceUID,
- segmentNumber,
- frameIndex: closest,
- frame,
- });
- }
- };
-
- const isSegmentVisible = () => {
- return !labelmap3D.segmentsHidden[segmentIndex];
- };
-
- const toggleSegmentVisibility = () => {
- const segmentsHidden = labelmap3D.segmentsHidden;
- segmentsHidden[segmentIndex] = !segmentsHidden[segmentIndex];
- return !segmentsHidden[segmentIndex];
- };
-
- const cachedSegmentProperties =
- state.cachedSegmentsProperties[segmentNumber];
- let visible = isSegmentVisible();
- if (
- cachedSegmentProperties &&
- cachedSegmentProperties.visible !== visible
- ) {
- toggleSegmentVisibility();
- }
-
- segmentList.push(
-
setCurrentSelectedSegment()}
- label={segmentLabel}
- index={segmentNumber}
- color={color}
- visible={visible}
- onVisibilityChange={newVisibility => {
- if (isCornerstone()) {
- const enabledElements = cornerstone.getEnabledElements();
- const element = enabledElements[activeIndex].element;
- module.setters.toggleSegmentVisibility(
- element,
- segmentNumber,
- brushStackState.activeLabelmapIndex
- );
- }
-
- if (isVTK()) {
- onSegmentVisibilityChange(segmentNumber, newVisibility);
- }
-
- updateCachedSegmentsProperties(segmentNumber, {
- visible: newVisibility,
- });
- refreshViewports();
- }}
- />
- );
- }
-
- return segmentList;
-
- /*
- * 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
- */
- },
- [activeIndex, onSegmentItemClick, state.selectedSegment, state.isLoading]
- );
-
- const updateCachedSegmentsProperties = (segmentNumber, properties) => {
- const segmentsProperties = state.cachedSegmentsProperties;
- const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
-
- segmentsProperties[segmentNumber] = segmentProperties
- ? { ...segmentProperties, ...properties }
- : properties;
-
- updateState('cachedSegmentsProperties', segmentsProperties);
+ setState(state => ({
+ ...state,
+ selectedSegmentation: activatedLabelmapIndex,
+ }));
+ },
+ };
+ });
};
- useEffect(() => {
- updateState('cachedSegmentsProperties', []);
- }, [activeContexts]);
+ const setCurrentSelectedSegment = segmentNumber => {
+ setActiveSegment(segmentNumber);
- const updateState = (field, value) => {
- setState(state => ({ ...state, [field]: value }));
+ const sameSegment = state.selectedSegment === segmentNumber;
+ if (!sameSegment) {
+ setState(state => ({ ...state, selectedSegment: segmentNumber }));
+ }
+
+ const validIndexList = [];
+ getActiveLabelMaps2D().forEach((labelMap2D, index) => {
+ if (labelMap2D.segmentsOnLabelmap.includes(segmentNumber)) {
+ validIndexList.push(index);
+ }
+ });
+
+ 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;
+ });
+
+ if (isCornerstone()) {
+ const element = getEnabledElement();
+ const toolState = cornerstoneTools.getToolState(element, 'stack');
+
+ if (!toolState) return;
+
+ const imageIds = toolState.data[0].imageIds;
+ const imageId = imageIds[closest];
+ const frameIndex = imageIds.indexOf(imageId);
+
+ const SOPInstanceUID = cornerstone.metaData.get(
+ 'SOPInstanceUID',
+ imageId
+ );
+ const StudyInstanceUID = cornerstone.metaData.get(
+ 'StudyInstanceUID',
+ imageId
+ );
+
+ DICOMSegTempCrosshairsTool.addCrosshair(element, imageId, segmentNumber);
+
+ onSegmentItemClick({
+ StudyInstanceUID,
+ SOPInstanceUID,
+ frameIndex,
+ activeViewportIndex: activeIndex,
+ });
+ }
+
+ 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,
+ });
+ }
+ };
+
+ const getColorLUTTable = () => {
+ const { state } = cornerstoneTools.getModule('segmentation');
+ const { colorLUTIndex } = getActiveLabelMaps3D();
+ return state.colorLutTables[colorLUTIndex];
+ };
+
+ const getEnabledElement = () => {
+ const enabledElements = cornerstone.getEnabledElements();
+ return enabledElements[activeIndex].element;
+ };
+
+ const onSegmentVisibilityChangeHandler = (isVisible, segmentNumber) => {
+ /** Get all labelmaps with this segmentNumber (overlapping segments) */
+ const { labelmaps3D } = getBrushStackState();
+ const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
+ return labelmaps2D.some(({ segmentsOnLabelmap }) =>
+ segmentsOnLabelmap.includes(segmentNumber)
+ );
+ });
+
+ let segmentsHidden = [];
+ possibleLabelMaps3D.forEach(labelmap3D => {
+ labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
+
+ segmentsHidden = [
+ ...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
+ ];
+ });
+
+ setState(state => ({ ...state, segmentsHidden }));
+
+ refreshSegmentations();
+ refreshViewports();
+
+ if (isVTK()) {
+ onSegmentVisibilityChange(segmentNumber, isVisible);
+ }
+ };
+
+ 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]);
+ }
+ }
+ }
+
+ 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(
+
+ );
+ }
+
+ 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
+ */
};
const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) {
- updateState('brushRadius', updatedRadius);
+ setState(state => ({ ...state, brushRadius: updatedRadius }));
const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius);
}
};
const decrementSegment = event => {
+ const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault();
- if (labelmap3D.activeSegmentIndex > 1) {
- labelmap3D.activeSegmentIndex--;
+ if (activeSegmentIndex > 1) {
+ activeSegmentIndex--;
}
- updateState('selectedSegment', labelmap3D.activeSegmentIndex);
+ setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor();
};
const incrementSegment = event => {
+ const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault();
- labelmap3D.activeSegmentIndex++;
- updateState('selectedSegment', labelmap3D.activeSegmentIndex);
+ activeSegmentIndex++;
+ setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor();
};
const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor();
- updateState('brushColor', color);
+ setState(state => ({ ...state, brushColor: color }));
+ };
+
+ const getBrushStackState = () => {
+ const module = cornerstoneTools.getModule('segmentation');
+ const firstImageId = getFirstImageId();
+ const brushStackState = module.state.series[firstImageId];
+ return brushStackState;
};
const getActiveSegmentColor = () => {
- if (!state.brushStackState) {
+ const brushStackState = getBrushStackState();
+ if (!brushStackState) {
return 'rgba(255, 255, 255, 1)';
}
- const module = cornerstoneTools.getModule('segmentation');
- const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
+ const colorLutTable = getColorLUTTable();
const color = colorLutTable[labelmap3D.activeSegmentIndex];
-
return `rgba(${color.join(',')})`;
};
@@ -520,65 +560,103 @@ const SegmentationPanel = ({
refreshViewports();
};
+ const onVisibilityChangeHandler = isVisible => {
+ let segmentsHidden = [];
+ state.segmentNumbers.forEach(segmentNumber => {
+ if (isVTK()) {
+ onSegmentVisibilityChange(segmentNumber, isVisible);
+ }
+
+ /** Get all labelmaps with this segmentNumber (overlapping segments) */
+ const { labelmaps3D } = getBrushStackState();
+ const possibleLabelMaps3D = labelmaps3D.filter(({ labelmaps2D }) => {
+ return labelmaps2D.some(({ segmentsOnLabelmap }) =>
+ segmentsOnLabelmap.includes(segmentNumber)
+ );
+ });
+
+ possibleLabelMaps3D.forEach(labelmap3D => {
+ labelmap3D.segmentsHidden[segmentNumber] = !isVisible;
+ segmentsHidden = [
+ ...new Set([...segmentsHidden, ...labelmap3D.segmentsHidden]),
+ ];
+ });
+ });
+
+ setState(state => ({ ...state, segmentsHidden }));
+
+ refreshSegmentations();
+ refreshViewports();
+ };
+
const disabledConfigurationFields = [
'outlineAlpha',
'shouldRenderInactiveLabelmaps',
];
- if (state.showSegmentationSettings) {
+
+ const selectedSegmentationOption = state.labelMapList.find(
+ i => i.value === state.selectedSegmentation
+ );
+
+ if (state.showSettings) {
return (
updateState('showSegmentationSettings', false)}
+ onBack={() => setState(state => ({ ...state, showSettings: false }))}
onChange={updateConfiguration}
/>
);
} else {
return (
-
updateState('showSegmentationSettings', true)}
- />
{false && (
)}
+ setState(state => ({ ...state, showSettings: true }))}
+ />
Segmentations
i.value === state.selectedSegmentation
- )}
+ value={selectedSegmentationOption}
formatOptionLabel={SegmentationItem}
- options={state.labelmapList}
+ options={state.labelMapList}
/>
-
- }
- >
- {state.segmentList}
-
-
+ isHidden === true).length <
+ state.segmentNumbers.length
+ }
+ onVisibilityChange={onVisibilityChangeHandler}
+ >
+
+ {state.segmentList}
+
+
);
}
@@ -613,7 +691,7 @@ SegmentationPanel.propTypes = {
SegmentationPanel.defaultProps = {};
/**
- * Returns SEG Displaysets that reference the target series, sorted by dateTime
+ * Returns SEG DisplaySets that reference the target series, sorted by dateTime
*
* @param {string} StudyInstanceUID
* @param {string} SeriesInstanceUID
@@ -637,56 +715,48 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets;
};
-/**
- *
- * @param {*} firstImageId
- * @param {*} activeSegmentIndex
- * @returns
- */
-const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex, activeViewport) => {
- if (segmentIndex === activeSegmentIndex) {
- log.info(`${activeSegmentIndex} is already the active segment`);
- return;
- }
+const SegmentsSection = ({
+ count,
+ children,
+ isVisible: defaultVisibility,
+ onVisibilityChange,
+}) => {
+ const [isVisible, setIsVisible] = useState(defaultVisibility);
- const { state } = cornerstoneTools.getModule('segmentation');
- const brushStackState = state.series[firstImageId];
+ const onVisibilityChangeHandler = () => {
+ const newVisibility = !isVisible;
+ setIsVisible(newVisibility);
+ onVisibilityChange(newVisibility);
+ };
- const labelmap3D =
- brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
- labelmap3D.activeSegmentIndex = segmentIndex;
+ useEffect(() => {
+ setIsVisible(defaultVisibility);
+ }, [defaultVisibility]);
- /**
- * 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 newLabelmapIndex = brushStackState.activeLabelmapIndex;
- allDisplaySets.forEach(displaySet => {
- if (displaySet.labelmapSegments) {
- Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
- if (displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)) {
- newLabelmapIndex = labelmapIndex;
- }
- });
- }
- });
-
- brushStackState.activeLabelmapIndex = newLabelmapIndex;
-
- refreshViewports();
- return segmentIndex;
-};
-
-const SegmentsHeader = ({ count }) => {
return (
-
- Segments
- {count}
-
+
);
};
+const noop = () => {};
+
+SegmentsSection.defaultProps = {
+ onVisibilityChange: noop,
+};
+
export default SegmentationPanel;
diff --git a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js
index f21c080f4..4d9435893 100644
--- a/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js
+++ b/extensions/dicom-segmentation/src/getOHIFDicomSegSopClassHandler.js
@@ -21,7 +21,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
id: 'OHIFDicomSegSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
- getDisplaySetFromSeries: function (
+ getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
@@ -55,6 +55,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded.
isLoaded: false,
+ hasOverlapping: false,
SeriesDate,
SeriesTime,
SeriesNumber,
@@ -62,11 +63,11 @@ export default function getSopClassHandlerModule({ servicesManager }) {
metadata,
};
- segDisplaySet.getSourceDisplaySet = function (studies) {
+ segDisplaySet.getSourceDisplaySet = function(studies) {
return getSourceDisplaySet(studies, segDisplaySet);
};
- segDisplaySet.load = async function (referencedDisplaySet, studies) {
+ segDisplaySet.load = async function(referencedDisplaySet, studies) {
segDisplaySet.isLoaded = true;
const { StudyInstanceUID } = referencedDisplaySet;
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
@@ -81,7 +82,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID
);
- return new Promise((resolve, reject) => {
+ return new Promise(async (resolve, reject) => {
let results;
try {
results = _parseSeg(segArrayBuffer, imageIds);
@@ -90,18 +91,47 @@ export default function getSopClassHandlerModule({ servicesManager }) {
segDisplaySet.loadError = true;
reject(error);
}
- const { labelmapBufferArray, segMetadata, segmentsOnFrame, segmentsOnFrameArray } = results;
+ const {
+ labelmapBufferArray,
+ segMetadata,
+ segmentsOnFrame,
+ segmentsOnFrameArray,
+ } = results;
+ let labelmapIndex;
if (labelmapBufferArray.length > 1) {
+ let labelmapIndexes = [];
for (let i = 0; i < labelmapBufferArray.length; ++i) {
- loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[i], segMetadata, segmentsOnFrame, segmentsOnFrameArray[i]);
+ labelmapIndexes.push(
+ await loadSegmentation(
+ imageIds,
+ segDisplaySet,
+ labelmapBufferArray[i],
+ segMetadata,
+ segmentsOnFrame,
+ segmentsOnFrameArray[i]
+ )
+ );
}
+ /**
+ * Since overlapping segmentations have virtual labelmaps,
+ * originLabelMapIndex is used in the panel to select the correct dropdown value.
+ */
+ segDisplaySet.hasOverlapping = true;
+ segDisplaySet.originLabelMapIndex = labelmapIndexes[0];
+ labelmapIndex = labelmapIndexes[0];
+ console.warn('Overlapping segmentations!');
} else {
- loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[0], segMetadata, segmentsOnFrame, []);
+ labelmapIndex = await loadSegmentation(
+ imageIds,
+ segDisplaySet,
+ labelmapBufferArray[0],
+ segMetadata,
+ segmentsOnFrame,
+ []
+ );
}
- segDisplaySet.labelmapIndex = 0;
-
- resolve();
+ resolve(labelmapIndex);
});
};
diff --git a/extensions/dicom-segmentation/src/loadSegmentation.js b/extensions/dicom-segmentation/src/loadSegmentation.js
index beb4f80ba..a655c0687 100644
--- a/extensions/dicom-segmentation/src/loadSegmentation.js
+++ b/extensions/dicom-segmentation/src/loadSegmentation.js
@@ -34,7 +34,11 @@ export default async function loadSegmentation(
* Cache each labelmap segments.
* This data is used to determine the active label map when a given segment is activated/clicked.
*/
- segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length ? Array.from(new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))) : [];
+ segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length
+ ? Array.from(
+ new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))
+ )
+ : [];
segDisplaySet.labelmapIndex = labelmapIndex;
/*
@@ -57,6 +61,8 @@ export default async function loadSegmentation(
console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event);
+
+ return labelmapIndex;
}
function _getNextLabelmapIndex(firstImageId) {
diff --git a/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js
index ec5535b3a..8a08aad10 100644
--- a/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js
+++ b/extensions/dicom-segmentation/src/utils/setActiveLabelMap.js
@@ -37,9 +37,14 @@ export default async function setActiveLabelmap(
? brushStackState.activeLabelmapIndex
: undefined;
- if (displaySet.labelmapIndex === activeLabelmapIndex) {
+ let labelmapIndex =
+ displaySet.hasOverlapping === true
+ ? displaySet.originLabelMapIndex
+ : displaySet.labelmapIndex;
+
+ if (labelmapIndex === activeLabelmapIndex) {
log.warn(`${activeLabelmapIndex} is already the active labelmap`);
- return displaySet.labelmapIndex;
+ return labelmapIndex;
}
if (!displaySet.isLoaded) {
@@ -55,13 +60,18 @@ export default async function setActiveLabelmap(
await loadPromise;
}
+ labelmapIndex =
+ displaySet.hasOverlapping === true
+ ? displaySet.originLabelMapIndex
+ : displaySet.labelmapIndex;
+
// This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId];
- brushStackState.activeLabelmapIndex = displaySet.labelmapIndex;
+ brushStackState.activeLabelmapIndex = labelmapIndex;
refreshViewports();
callback();
- return displaySet.labelmapIndex;
+ return labelmapIndex;
}
diff --git a/extensions/vtk/CHANGELOG.md b/extensions/vtk/CHANGELOG.md
index a6ee3b762..f905f7e3f 100644
--- a/extensions/vtk/CHANGELOG.md
+++ b/extensions/vtk/CHANGELOG.md
@@ -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.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.1...@ohif/extension-vtk@1.10.2) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/extension-vtk
+
+
+
+
+
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.0...@ohif/extension-vtk@1.10.1) (2020-12-10)
diff --git a/extensions/vtk/package.json b/extensions/vtk/package.json
index 1b1836659..1b3c8b35a 100644
--- a/extensions/vtk/package.json
+++ b/extensions/vtk/package.json
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
- "version": "1.10.1",
+ "version": "1.10.2",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2"
},
"devDependencies": {
- "@ohif/core": "^2.12.0",
- "@ohif/ui": "^1.8.0",
+ "@ohif/core": "^2.12.1",
+ "@ohif/ui": "^1.8.1",
"cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3",
diff --git a/extensions/vtk/src/OHIFVTKViewport.js b/extensions/vtk/src/OHIFVTKViewport.js
index 19e557143..bac284a4e 100644
--- a/extensions/vtk/src/OHIFVTKViewport.js
+++ b/extensions/vtk/src/OHIFVTKViewport.js
@@ -72,7 +72,7 @@ class OHIFVTKViewport extends Component {
};
static defaultProps = {
- onScroll: () => { },
+ onScroll: () => {},
};
static id = 'OHIFVTKViewport';
@@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component {
SOPInstanceUID,
frameIndex
) => {
+ const { UINotificationService } = this.props.servicesManager.services;
+
const stack = OHIFVTKViewport.getCornerstoneStack(
studies,
StudyInstanceUID,
@@ -159,11 +161,14 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
- if (brushStackState.labelmaps3D.length > 1 && this.props.viewportIndex === 0) {
- const { UINotificationService } = this.props.servicesManager.services;
+ if (
+ brushStackState.labelmaps3D.length > 1 &&
+ this.props.viewportIndex === 0
+ ) {
UINotificationService.show({
title: 'Overlapping Segmentation Found',
- message: 'Overlapping segmentations cannot be displayed when in MPR mode',
+ message:
+ 'Overlapping segmentations cannot be displayed when in MPR mode',
type: 'info',
});
}
@@ -305,52 +310,79 @@ class OHIFVTKViewport extends Component {
seriesDescription: displaySet.seriesDescription,
};
- const {
- imageDataObject,
- labelmapDataObject,
- labelmapColorLUT,
- } = this.getViewportData(
- studies,
- StudyInstanceUID,
- displaySetInstanceUID,
- SOPInstanceUID,
- frameIndex
- );
+ try {
+ const {
+ imageDataObject,
+ labelmapDataObject,
+ labelmapColorLUT,
+ } = this.getViewportData(
+ studies,
+ StudyInstanceUID,
+ displaySetInstanceUID,
+ SOPInstanceUID,
+ frameIndex
+ );
- this.imageDataObject = imageDataObject;
+ this.imageDataObject = imageDataObject;
- /* TODO: Not currently used until we have drawing tools in vtkjs.
- if (!labelmap) {
- labelmap = createLabelMapImageData(data);
- } */
+ /* TODO: Not currently used until we have drawing tools in vtkjs.
+ if (!labelmap) {
+ labelmap = createLabelMapImageData(data);
+ } */
- const volumeActor = this.getOrCreateVolume(
- imageDataObject,
- displaySetInstanceUID
- );
+ const volumeActor = this.getOrCreateVolume(
+ imageDataObject,
+ displaySetInstanceUID
+ );
- this.setState(
- {
- percentComplete: 0,
- dataDetails,
- },
- () => {
- this.loadProgressively(imageDataObject);
+ this.setState(
+ {
+ percentComplete: 0,
+ dataDetails,
+ },
+ () => {
+ this.loadProgressively(imageDataObject);
- // TODO: There must be a better way to do this.
- // We do this so that if all the data is available the react-vtkjs-viewport
- // Will render _something_ before the volumes are set and the volume
- // Construction that happens in react-vtkjs-viewport locks up the CPU.
- setTimeout(() => {
- this.setState({
- volumes: [volumeActor],
- paintFilterLabelMapImageData: labelmapDataObject,
- paintFilterBackgroundImageData: imageDataObject.vtkImageData,
- labelmapColorLUT,
- });
- }, 200);
+ // TODO: There must be a better way to do this.
+ // We do this so that if all the data is available the react-vtkjs-viewport
+ // Will render _something_ before the volumes are set and the volume
+ // Construction that happens in react-vtkjs-viewport locks up the CPU.
+ setTimeout(() => {
+ this.setState({
+ volumes: [volumeActor],
+ paintFilterLabelMapImageData: labelmapDataObject,
+ paintFilterBackgroundImageData: imageDataObject.vtkImageData,
+ labelmapColorLUT,
+ });
+ }, 200);
+ }
+ );
+ } catch (error) {
+ const errorTitle = 'Failed to load 2D MPR';
+ console.error(errorTitle, error);
+ const { UINotificationService } = this.props.servicesManager.services;
+ if (this.props.viewportIndex === 0) {
+ const message = error.message.includes('buffer')
+ ? 'Dataset is too big to display in MPR'
+ : error.message;
+ console.error(errorTitle, error);
+ UINotificationService.show({
+ title: errorTitle,
+ message,
+ type: 'error',
+ autoClose: false,
+ action: {
+ label: 'Exit 2D MPR',
+ onClick: ({ close }) => {
+ // context: 'ACTIVE_VIEWPORT::VTK',
+ close();
+ this.props.commandsManager.runCommand('setCornerstoneLayout');
+ },
+ },
+ });
}
- );
+ this.setState({ isLoaded: true });
+ }
}
componentDidMount() {
@@ -363,7 +395,7 @@ class OHIFVTKViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
- prevDisplaySet.displaySetInstanceUID ||
+ prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
diff --git a/extensions/vtk/src/index.js b/extensions/vtk/src/index.js
index dde0d6ce0..18e69c22c 100644
--- a/extensions/vtk/src/index.js
+++ b/extensions/vtk/src/index.js
@@ -9,7 +9,9 @@ import { version } from '../package.json';
// import loadLocales from './loadLocales';
const OHIFVTKViewport = asyncComponent(() =>
- retryImport(() => import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js'))
+ retryImport(() =>
+ import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
+ )
);
const vtkExtension = {
@@ -21,7 +23,11 @@ const vtkExtension = {
getViewportModule({ commandsManager, servicesManager }) {
const ExtendedVTKViewport = props => (
-
+
);
return withCommandsManager(ExtendedVTKViewport, commandsManager);
},
diff --git a/platform/core/CHANGELOG.md b/platform/core/CHANGELOG.md
index afd6f5d5e..bf41447f6 100644
--- a/platform/core/CHANGELOG.md
+++ b/platform/core/CHANGELOG.md
@@ -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.
+## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.0...@ohif/core@2.12.1) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/core
+
+
+
+
+
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)
diff --git a/platform/core/package.json b/platform/core/package.json
index 71ad0313a..ba48fd021 100644
--- a/platform/core/package.json
+++ b/platform/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
- "version": "2.12.0",
+ "version": "2.12.1",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
diff --git a/platform/core/src/services/UINotificationService/index.js b/platform/core/src/services/UINotificationService/index.js
index 0cdb8ea95..42e4b0512 100644
--- a/platform/core/src/services/UINotificationService/index.js
+++ b/platform/core/src/services/UINotificationService/index.js
@@ -8,6 +8,7 @@
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true]
+ * @property {object} [action=null]
*/
const name = 'UINotificationService';
@@ -34,7 +35,7 @@ const serviceImplementation = {
* Create and show a new UI notification; returns the
* ID of the created notification.
*
- * @param {Notification} notification { title, message, duration, position, type, autoClose}
+ * @param {Notification} notification { title, message, duration, position, type, autoClose, action}
* @returns {number} id
*/
function _show({
@@ -44,6 +45,7 @@ function _show({
position = 'bottomRight',
type = 'info',
autoClose = true,
+ action = null,
}) {
return serviceImplementation._show({
title,
@@ -52,6 +54,7 @@ function _show({
position,
type,
autoClose,
+ action,
});
}
diff --git a/platform/ui/CHANGELOG.md b/platform/ui/CHANGELOG.md
index 964bae730..5dc90a2dd 100644
--- a/platform/ui/CHANGELOG.md
+++ b/platform/ui/CHANGELOG.md
@@ -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.8.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.0...@ohif/ui@1.8.1) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/ui
+
+
+
+
+
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)
diff --git a/platform/ui/package.json b/platform/ui/package.json
index cb97e11d3..3fe076c90 100644
--- a/platform/ui/package.json
+++ b/platform/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
- "version": "1.8.0",
+ "version": "1.8.1",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
diff --git a/platform/ui/src/components/snackbar/Snackbar.css b/platform/ui/src/components/snackbar/Snackbar.css
index bb99cefe6..8d7399165 100644
--- a/platform/ui/src/components/snackbar/Snackbar.css
+++ b/platform/ui/src/components/snackbar/Snackbar.css
@@ -156,19 +156,50 @@
transition: all 300ms ease;
}
-.sb-success {
+.sb-item .sb-action,
+.sb-error .sb-action,
+.sb-warning .sb-action,
+.sb-info .sb-action,
+.sb-success .sb-action {
+ margin-top: 10px;
+ display: inline-block;
+ padding: 6px 12px;
+ margin-bottom: 0;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.42857143;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-image: none;
+ border: none;
+ color: white;
+}
+
+.sb-success,
+.sb-success .sb-action {
background-color: var(--snackbar-success);
}
-.sb-error {
+.sb-error,
+.sb-error .sb-action {
background-color: var(--snackbar-error);
}
-.sb-warning {
+.sb-warning,
+.sb-warning .sb-action {
background-color: var(--snackbar-warning);
}
-.sb-info {
+.sb-info,
+.sb-info .sb-action {
background-color: var(--snackbar-info);
}
diff --git a/platform/ui/src/components/snackbar/SnackbarItem.js b/platform/ui/src/components/snackbar/SnackbarItem.js
index 40c43697e..2e1d62ccf 100644
--- a/platform/ui/src/components/snackbar/SnackbarItem.js
+++ b/platform/ui/src/components/snackbar/SnackbarItem.js
@@ -1,10 +1,14 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => {
onClose(options.id);
};
+ const handleClick = () => {
+ options.action.onClick({ ...options, close: handleClose });
+ };
+
useEffect(() => {
if (options.autoClose) {
setTimeout(() => {
@@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => {
{options.title && {options.title}
}
{options.message && {options.message}
}
+ {options.action && (
+
+ {options.action.label}
+
+ )}
);
};
diff --git a/platform/ui/src/contextProviders/SnackbarProvider.js b/platform/ui/src/contextProviders/SnackbarProvider.js
index 4b4c6bcbd..86709fedc 100644
--- a/platform/ui/src/contextProviders/SnackbarProvider.js
+++ b/platform/ui/src/contextProviders/SnackbarProvider.js
@@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => {
autoClose: true,
position: 'bottomRight',
type: SnackbarTypes.INFO,
+ action: null,
};
const [count, setCount] = useState(1);
diff --git a/platform/viewer/CHANGELOG.md b/platform/viewer/CHANGELOG.md
index d99d6d4c8..797462e0b 100644
--- a/platform/viewer/CHANGELOG.md
+++ b/platform/viewer/CHANGELOG.md
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [4.8.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.3...@ohif/viewer@4.8.4) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/viewer
+
+
+
+
+
+## [4.8.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.2...@ohif/viewer@4.8.3) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/viewer
+
+
+
+
+
+## [4.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.1...@ohif/viewer@4.8.2) (2021-01-21)
+
+**Note:** Version bump only for package @ohif/viewer
+
+
+
+
+
## [4.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.0...@ohif/viewer@4.8.1) (2020-12-10)
**Note:** Version bump only for package @ohif/viewer
diff --git a/platform/viewer/package.json b/platform/viewer/package.json
index 7defee060..4a7a6221a 100644
--- a/platform/viewer/package.json
+++ b/platform/viewer/package.json
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
- "version": "4.8.1",
+ "version": "4.8.4",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -49,18 +49,18 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
- "@ohif/core": "^2.12.0",
+ "@ohif/core": "^2.12.1",
"@ohif/extension-cornerstone": "^2.10.0",
"@ohif/extension-debugging": "^0.1.6",
"@ohif/extension-dicom-html": "^1.3.0",
- "@ohif/extension-dicom-microscopy": "^0.51.3",
+ "@ohif/extension-dicom-microscopy": "^0.51.4",
"@ohif/extension-dicom-pdf": "^1.0.6",
- "@ohif/extension-dicom-rt": "^0.6.1",
- "@ohif/extension-dicom-segmentation": "^0.5.0",
+ "@ohif/extension-dicom-rt": "^0.6.2",
+ "@ohif/extension-dicom-segmentation": "^0.5.1",
"@ohif/extension-lesion-tracker": "^0.2.1",
- "@ohif/extension-vtk": "^1.10.1",
+ "@ohif/extension-vtk": "^1.10.2",
"@ohif/i18n": "^0.53.0",
- "@ohif/ui": "^1.8.0",
+ "@ohif/ui": "^1.8.1",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",