erge branch 'master' of github.com:OHIF/Viewers into IDC-2123
This commit is contained in:
igoroctaviano 2021-01-21 14:15:13 -03:00
commit 7f8cf5f5da
32 changed files with 915 additions and 553 deletions

View File

@ -121,8 +121,7 @@ window.config = {
}; };
``` ```
- Install the viewer: - Install the viewer: `window.OHIFViewer.installViewer(window.config);`
`window.OHIFViewer.installViewer(window.config);`
This exact setup is demonstrated in this This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our [CodeSandbox](https://codesandbox.io/s/viewer-script-tag-tprch) and in our
@ -249,6 +248,9 @@ To acknowledge the OHIF Viewer in an academic publication, please cite
> >
> _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: > _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI:
> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334)
>
> NCI Imaging Data Commons: contract number 19X037Q from Leidos Biomedical
> Research under Task Order HHSN26100071 from NCI.
**Note:** If you use or find this repository helpful, please take the time to **Note:** If you use or find this repository helpful, please take the time to
star this repository on Github. This is an easy way for us to assess adoption star this repository on Github. This is an easy way for us to assess adoption

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.3...@ohif/extension-dicom-microscopy@0.51.4) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10) ## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
**Note:** Version bump only for package @ohif/extension-dicom-microscopy **Note:** Version bump only for package @ohif/extension-dicom-microscopy

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "0.51.3", "version": "0.51.4",
"description": "OHIF extension for Dicom Microscopy", "description": "OHIF extension for Dicom Microscopy",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -62,11 +62,24 @@ class DicomMicroscopyViewport extends Component {
); );
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer; const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
try {
this.viewer = new microscopyViewer({ this.viewer = new microscopyViewer({
client: dicomWebClient, client: dicomWebClient,
metadata, metadata,
retrieveRendered: false, retrieveRendered: false,
}); });
} catch (error) {
console.error('[Microscopy Viewer] Failed to load:', error);
const { UINotificationService } = this.props.servicesManager.services;
if (UINotificationService) {
UINotificationService.show({
title: 'Microscopy Viewport',
message:
'Failed to load viewport. Please check that you have hardware acceleration enabled.',
type: 'error',
});
}
}
this.viewer.render({ container }); this.viewer.render({ container });
}); });

View File

@ -6,14 +6,6 @@ const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport'); return import('./DicomMicroscopyViewport');
}); });
const DicomMicroscopyViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
export default { export default {
/** /**
* Only required property. Should be a unique value across all extensions. * Only required property. Should be a unique value across all extensions.
@ -21,8 +13,14 @@ export default {
id: 'microscopy', id: 'microscopy',
version, version,
getViewportModule() { getViewportModule({ servicesManager }) {
return DicomMicroscopyViewport; return props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} servicesManager={servicesManager} />
</React.Suspense>
);
};
}, },
getSopClassHandlerModule() { getSopClassHandlerModule() {
return DicomMicroscopySopClassHandler; return DicomMicroscopySopClassHandler;

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.1...@ohif/extension-dicom-rt@0.6.2) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-rt
## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.0...@ohif/extension-dicom-rt@0.6.1) (2020-12-10) ## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.0...@ohif/extension-dicom-rt@0.6.1) (2020-12-10)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-rt", "name": "@ohif/extension-dicom-rt",
"version": "0.6.1", "version": "0.6.2",
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.", "description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -39,6 +39,8 @@
border-radius: 5px; border-radius: 5px;
} }
.icons .angle-double-down, .icons .angle-double-up { .icons .angle-double-down,
.icons .angle-double-up {
margin-left: 15px; margin-left: 15px;
margin-right: -5px;
} }

View File

@ -9,16 +9,21 @@
.dcmrt-panel .dcmrt-panel-header { .dcmrt-panel .dcmrt-panel-header {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: space-between; justify-content: space-between;
color: white; color: white;
} }
.dcmrt-panel .dcmrt-panel-header h3 {
margin-top: 0;
}
.dcmrt-panel .dcmrt-panel-header .cog-icon { .dcmrt-panel .dcmrt-panel-header .cog-icon {
align-self: flex-end; align-self: flex-end;
color: var(--default-color); color: var(--default-color);
cursor: pointer; cursor: pointer;
margin-bottom: 20px; min-height: 23px;
max-height: 23px;
} }
.dcmrt-panel .dcmrt-panel-header .cog-icon:hover { .dcmrt-panel .dcmrt-panel-header .cog-icon:hover {

View File

@ -44,7 +44,7 @@ const RTPanel = ({
activeContexts = [], activeContexts = [],
contexts = {}, contexts = {},
activeViewport, activeViewport,
getActiveViewport getActiveViewport,
}) => { }) => {
const isVTK = () => activeContexts.includes(contexts.VTK); const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE); const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@ -55,7 +55,7 @@ const RTPanel = ({
referencedDisplaysets: [], referencedDisplaysets: [],
sets: [], sets: [],
selectedSet: null, selectedSet: null,
isLocked: false isLocked: false,
}; };
const [state, setState] = useState(DEFAULT_STATE); const [state, setState] = useState(DEFAULT_STATE);
@ -84,12 +84,17 @@ const RTPanel = ({
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets); document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => { return () => {
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets); document.removeEventListener(
'extensiondicomrtrtloaded',
updateStructureSets
);
}; };
}, []); }, []);
useEffect(() => { useEffect(() => {
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID); const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({ const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID, referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT', Modality: 'RTSTRUCT',
@ -235,7 +240,7 @@ const RTPanel = ({
return ( return (
<div className="dcmrt-panel"> <div className="dcmrt-panel">
<div className="dcmrt-panel-header"> <div className="dcmrt-panel-header">
<h3>RT Structure Sets</h3> {' '}
{!state.isLocked && ( {!state.isLocked && (
<Icon <Icon
className="cog-icon" className="cog-icon"
@ -245,9 +250,13 @@ const RTPanel = ({
onClick={() => setShowSettings(true)} onClick={() => setShowSettings(true)}
/> />
)} )}
<h3>RT Structure Sets</h3>
</div> </div>
{!state.isLocked && !state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />} {!state.isLocked && !state.referencedDisplaysets.length && (
{state.sets && state.referencedDisplaysets.map(displaySet => { <LoadingIndicator expand height="70px" width="70px" />
)}
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet; const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct'); const module = cornerstoneTools.getModule('rtstruct');

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.5.0...@ohif/extension-dicom-segmentation@0.5.1) (2021-01-21)
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10) # [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-dicom-segmentation", "name": "@ohif/extension-dicom-segmentation",
"version": "0.5.0", "version": "0.5.1",
"description": "OHIF extension for viewing segmentations in the 2D MPR view", "description": "OHIF extension for viewing segmentations in the 2D MPR view",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",

View File

@ -24,7 +24,7 @@ const SegmentItem = ({
onClick, onClick,
itemClass, itemClass,
color, color,
visible = true, visible,
onVisibilityChange, onVisibilityChange,
}) => { }) => {
const [isVisible, setIsVisible] = useState(visible); const [isVisible, setIsVisible] = useState(visible);
@ -33,6 +33,15 @@ const SegmentItem = ({
setIsVisible(visible); setIsVisible(visible);
}, [visible]); }, [visible]);
const onClickHandler = () => onClick(index);
const onVisibilityChangeHandler = event => {
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility, index);
};
return ( return (
<div className="dcmseg-segment-item"> <div className="dcmseg-segment-item">
<TableListItem <TableListItem
@ -42,7 +51,7 @@ const SegmentItem = ({
itemClass={itemClass} itemClass={itemClass}
itemMeta={<ColoredCircle color={color} />} itemMeta={<ColoredCircle color={color} />}
itemMetaClass="segment-color-section" itemMetaClass="segment-color-section"
onItemClick={onClick} onItemClick={onClickHandler}
> >
<div> <div>
<div className="segment-label" style={{ marginBottom: 4 }}> <div className="segment-label" style={{ marginBottom: 4 }}>
@ -63,12 +72,7 @@ const SegmentItem = ({
name={isVisible ? 'eye' : 'eye-closed'} name={isVisible ? 'eye' : 'eye-closed'}
width="20px" width="20px"
height="20px" height="20px"
onClick={event => { onClick={onVisibilityChangeHandler}
event.stopPropagation();
const newVisibility = !isVisible;
setIsVisible(newVisibility);
onVisibilityChange(newVisibility);
}}
/> />
</div> </div>
{false && <div className="segment-info">{'...'}</div>} {false && <div className="segment-info">{'...'}</div>}

View File

@ -6,6 +6,7 @@
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px;
} }
.dcmseg-segmentation-panel.disabled { .dcmseg-segmentation-panel.disabled {
@ -14,19 +15,19 @@
} }
.dcmseg-segmentation-panel h3 { .dcmseg-segmentation-panel h3 {
padding-left: 20px;
margin-top: 0; margin-top: 0;
} }
.dcmseg-segmentation-panel .segmentations { .dcmseg-segmentation-panel .segmentations {
padding: 0 20px 20px 20px; padding-bottom: 10px;
} }
.dcmseg-segmentation-panel .cog-icon { .dcmseg-segmentation-panel .cog-icon {
margin: 20px 20px 0 0;
align-self: flex-end; align-self: flex-end;
color: var(--default-color); color: var(--default-color);
cursor: pointer; cursor: pointer;
min-height: 23px;
max-height: 23px;
} }
.dcmseg-segmentation-panel .cog-icon:hover { .dcmseg-segmentation-panel .cog-icon:hover {
@ -64,3 +65,61 @@
flex: unset; flex: unset;
margin-right: 16px; 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;
}

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
import cornerstone from 'cornerstone-core'; import cornerstone from 'cornerstone-core';
import moment from 'moment'; import moment from 'moment';
import classNames from 'classnames';
import { utils, log } from '@ohif/core'; import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui'; import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool'; import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
@ -67,29 +68,104 @@ const SegmentationPanel = ({
const [state, setState] = useState({ const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS, brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor: 'rgba(221, 85, 85, 1)', brushColor: 'rgba(221, 85, 85, 1)',
selectedSegment: null, selectedSegment: 0,
selectedSegmentation: null, selectedSegmentation: 0,
showSegmentationSettings: false, showSettings: false,
brushStackState: null, labelMapList: [],
labelmapList: [],
segmentList: [], segmentList: [],
cachedSegmentsProperties: [], segmentsHidden: [],
segmentNumbers: [],
isLoading: false, isLoading: false,
isDisabled: true, 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(() => { useEffect(() => {
const labelmapModifiedHandler = event => { const labelmapModifiedHandler = event => {
log.warn('Segmentation Panel: labelmap modified', event); log.warn('Segmentation Panel: labelmap modified', event);
const module = cornerstoneTools.getModule('segmentation'); refreshSegmentations();
const activeViewport = viewports[activeIndex];
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
updateState('brushStackState', module.state.series[firstImageId]);
}; };
/* /*
@ -140,50 +216,38 @@ const SegmentationPanel = ({
}; };
}, [activeIndex, viewports]); }, [activeIndex, viewports]);
const refreshSegmentations = useCallback(() => { const refreshSegmentations = () => {
const module = cornerstoneTools.getModule('segmentation'); const activeViewport = getActiveViewport();
const activeViewport = viewports[activeIndex];
const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID; const isDisabled = !activeViewport || !activeViewport.StudyInstanceUID;
if (!isDisabled) { if (!isDisabled) {
const studyMetadata = studyMetadataManager.get( const brushStackState = getBrushStackState();
activeViewport.StudyInstanceUID
);
const firstImageId = studyMetadata.getFirstImageId(
activeViewport.displaySetInstanceUID
);
const brushStackState = module.state.series[firstImageId];
if (brushStackState) { if (brushStackState) {
const labelmap3D = const labelMapList = getLabelMapList();
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex]; const {
const labelmapList = getLabelmapList( items: segmentList,
brushStackState, numbers: segmentNumbers,
firstImageId, segmentsHidden,
activeViewport, } = getSegmentList();
);
const segmentList = getSegmentList(
labelmap3D,
firstImageId,
brushStackState,
);
setState(state => ({ setState(state => ({
...state, ...state,
brushStackState, segmentsHidden,
selectedSegmentation: brushStackState.activeLabelmapIndex, segmentNumbers,
labelmapList, labelMapList,
segmentList, segmentList,
isDisabled, isDisabled,
})); }));
} else { } else {
setState(state => ({ setState(state => ({
...state, ...state,
labelmapList: [], segmentsHidden: [],
segmentNumbers: [],
labelMapList: [],
segmentList: [], segmentList: [],
isDisabled, isDisabled,
})); }));
} }
} }
}, [viewports, activeIndex, state.isLoading]); };
useEffect(() => { useEffect(() => {
refreshSegmentations(); refreshSegmentations();
@ -200,12 +264,13 @@ const SegmentationPanel = ({
useEffect(() => { useEffect(() => {
setState(state => ({ setState(state => ({
...state, ...state,
showSegmentationSettings: state.showSegmentationSettings && !isOpen, showSettings: state.showSettings && !isOpen,
})); }));
}, [isOpen]); }, [isOpen]);
const getLabelmapList = useCallback( const getLabelMapList = () => {
(brushStackState, firstImageId, activeViewport) => { const activeViewport = getActiveViewport();
/* Get list of SEG labelmaps specific to active viewport (reference series) */ /* Get list of SEG labelmaps specific to active viewport (reference series) */
const referencedSegDisplaysets = _getReferencedSegDisplaysets( const referencedSegDisplaysets = _getReferencedSegDisplaysets(
activeViewport.StudyInstanceUID, activeViewport.StudyInstanceUID,
@ -213,19 +278,22 @@ const SegmentationPanel = ({
); );
return referencedSegDisplaysets.map((displaySet, index) => { return referencedSegDisplaysets.map((displaySet, index) => {
const { labelmapIndex, SeriesDate, SeriesTime } = displaySet; const {
labelmapIndex,
originLabelMapIndex,
hasOverlapping,
SeriesDate,
SeriesTime,
} = displaySet;
/* Map to display representation */ /* Map to display representation */
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0]; const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
const date = moment(dateStr, 'YYYYMMDD:HHmmss'); const date = moment(dateStr, 'YYYYMMDD:HHmmss');
const isActiveLabelmap =
labelmapIndex === brushStackState.activeLabelmapIndex;
const displayDate = date.format('ddd, MMM Do YYYY'); const displayDate = date.format('ddd, MMM Do YYYY');
const displayTime = date.format('h:mm:ss a');
const displayDescription = displaySet.SeriesDescription; const displayDescription = displaySet.SeriesDescription;
return { return {
value: labelmapIndex, value: hasOverlapping === true ? originLabelMapIndex : labelmapIndex,
title: displayDescription, title: displayDescription,
description: displayDate, description: displayDate,
onClick: async () => { onClick: async () => {
@ -233,25 +301,132 @@ const SegmentationPanel = ({
activeViewport, activeViewport,
studies, studies,
displaySet, displaySet,
() => onSelectedSegmentationChange(), onSelectedSegmentationChange,
onDisplaySetLoadFailure onDisplaySetLoadFailure
); );
updateState('selectedSegmentation', activatedLabelmapIndex); setState(state => ({
...state,
selectedSegmentation: activatedLabelmapIndex,
}));
}, },
}; };
}); });
}, };
[studies]
const setCurrentSelectedSegment = segmentNumber => {
setActiveSegment(segmentNumber);
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
); );
const getSegmentList = useCallback( DICOMSegTempCrosshairsTool.addCrosshair(element, imageId, segmentNumber);
(labelmap3D, firstImageId, brushStackState) => {
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` * Newly created segments have no `meta`
* So we instead build a list of all segment indexes in use * So we instead build a list of all segment indexes in use
* Then find any associated metadata * Then find any associated metadata
*/ */
const uniqueSegmentIndexes = labelmap3D.labelmaps2D const uniqueSegmentIndexes = getActiveLabelMaps2D()
.reduce((acc, labelmap2D) => { .reduce((acc, labelmap2D) => {
if (labelmap2D) { if (labelmap2D) {
const segmentIndexes = labelmap2D.segmentsOnLabelmap; const segmentIndexes = labelmap2D.segmentsOnLabelmap;
@ -267,12 +442,12 @@ const SegmentationPanel = ({
}, []) }, [])
.sort((a, b) => a - b); .sort((a, b) => a - b);
const module = cornerstoneTools.getModule('segmentation'); const labelmap3D = getActiveLabelMaps3D();
const colorLutTable = const colorLutTable = getColorLUTTable();
module.state.colorLutTables[labelmap3D.colorLUTIndex];
const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data; const hasLabelmapMeta = labelmap3D.metadata && labelmap3D.metadata.data;
const segmentList = []; const segmentList = [];
const segmentNumbers = [];
for (let i = 0; i < uniqueSegmentIndexes.length; i++) { for (let i = 0; i < uniqueSegmentIndexes.length; i++) {
const segmentIndex = uniqueSegmentIndexes[i]; const segmentIndex = uniqueSegmentIndexes[i];
@ -283,7 +458,6 @@ const SegmentationPanel = ({
/* Meta */ /* Meta */
if (hasLabelmapMeta) { if (hasLabelmapMeta) {
const segmentMeta = labelmap3D.metadata.data[segmentIndex]; const segmentMeta = labelmap3D.metadata.data[segmentIndex];
if (segmentMeta) { if (segmentMeta) {
segmentNumber = segmentMeta.SegmentNumber; segmentNumber = segmentMeta.SegmentNumber;
segmentLabel = segmentMeta.SegmentLabel; segmentLabel = segmentMeta.SegmentLabel;
@ -291,148 +465,27 @@ const SegmentationPanel = ({
} }
const sameSegment = state.selectedSegment === segmentNumber; const sameSegment = state.selectedSegment === segmentNumber;
const setCurrentSelectedSegment = () => {
const activeViewport = viewports[activeIndex];
_setActiveSegment(
firstImageId,
segmentNumber,
labelmap3D.activeSegmentIndex,
activeViewport
);
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();
}
segmentNumbers.push(segmentNumber);
segmentList.push( segmentList.push(
<SegmentItem <SegmentItem
key={segmentNumber} key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`} itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={() => setCurrentSelectedSegment()} onClick={setCurrentSelectedSegment}
label={segmentLabel} label={segmentLabel}
index={segmentNumber} index={segmentNumber}
color={color} color={color}
visible={visible} visible={!labelmap3D.segmentsHidden[segmentIndex]}
onVisibilityChange={newVisibility => { onVisibilityChange={onSegmentVisibilityChangeHandler}
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; return {
items: segmentList,
numbers: segmentNumbers,
segmentsHidden: labelmap3D.segmentsHidden,
};
/* /*
* Let's iterate over segmentIndexes ^ above * Let's iterate over segmentIndexes ^ above
@ -440,69 +493,56 @@ const SegmentationPanel = ({
* If now, add "no-meta" class * If now, add "no-meta" class
* Show default name * 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);
};
useEffect(() => {
updateState('cachedSegmentsProperties', []);
}, [activeContexts]);
const updateState = (field, value) => {
setState(state => ({ ...state, [field]: value }));
}; };
const updateBrushSize = evt => { const updateBrushSize = evt => {
const updatedRadius = Number(evt.target.value); const updatedRadius = Number(evt.target.value);
if (updatedRadius !== brushRadius) { if (updatedRadius !== brushRadius) {
updateState('brushRadius', updatedRadius); setState(state => ({ ...state, brushRadius: updatedRadius }));
const module = cornerstoneTools.getModule('segmentation'); const module = cornerstoneTools.getModule('segmentation');
module.setters.radius(updatedRadius); module.setters.radius(updatedRadius);
} }
}; };
const decrementSegment = event => { const decrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault(); event.preventDefault();
if (labelmap3D.activeSegmentIndex > 1) { if (activeSegmentIndex > 1) {
labelmap3D.activeSegmentIndex--; activeSegmentIndex--;
} }
updateState('selectedSegment', labelmap3D.activeSegmentIndex); setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor(); updateActiveSegmentColor();
}; };
const incrementSegment = event => { const incrementSegment = event => {
const activeSegmentIndex = getActiveSegmentIndex();
event.preventDefault(); event.preventDefault();
labelmap3D.activeSegmentIndex++; activeSegmentIndex++;
updateState('selectedSegment', labelmap3D.activeSegmentIndex); setState(state => ({ ...state, selectedSegment: activeSegmentIndex }));
updateActiveSegmentColor(); updateActiveSegmentColor();
}; };
const updateActiveSegmentColor = () => { const updateActiveSegmentColor = () => {
const color = getActiveSegmentColor(); 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 = () => { const getActiveSegmentColor = () => {
if (!state.brushStackState) { const brushStackState = getBrushStackState();
if (!brushStackState) {
return 'rgba(255, 255, 255, 1)'; return 'rgba(255, 255, 255, 1)';
} }
const module = cornerstoneTools.getModule('segmentation'); const colorLutTable = getColorLUTTable();
const colorLutTable = module.state.colorLutTables[labelmap3D.colorLUTIndex];
const color = colorLutTable[labelmap3D.activeSegmentIndex]; const color = colorLutTable[labelmap3D.activeSegmentIndex];
return `rgba(${color.join(',')})`; return `rgba(${color.join(',')})`;
}; };
@ -520,65 +560,103 @@ const SegmentationPanel = ({
refreshViewports(); 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 = [ const disabledConfigurationFields = [
'outlineAlpha', 'outlineAlpha',
'shouldRenderInactiveLabelmaps', 'shouldRenderInactiveLabelmaps',
]; ];
if (state.showSegmentationSettings) {
const selectedSegmentationOption = state.labelMapList.find(
i => i.value === state.selectedSegmentation
);
if (state.showSettings) {
return ( return (
<SegmentationSettings <SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []} disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration} configuration={configuration}
onBack={() => updateState('showSegmentationSettings', false)} onBack={() => setState(state => ({ ...state, showSettings: false }))}
onChange={updateConfiguration} onChange={updateConfiguration}
/> />
); );
} else { } else {
return ( return (
<div <div
className={`dcmseg-segmentation-panel ${state.isDisabled && className={classNames('dcmseg-segmentation-panel', {
'disabled'}`} disabled: state.isDisabled,
})}
> >
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => updateState('showSegmentationSettings', true)}
/>
{false && ( {false && (
<form className="selector-form"> <form className="selector-form">
<BrushColorSelector <BrushColorSelector
defaultColor={brushColor} defaultColor={state.brushColor}
index={state.selectedSegment} index={state.selectedSegment}
onNext={incrementSegment} onNext={incrementSegment}
onPrev={decrementSegment} onPrev={decrementSegment}
/> />
<BrushRadius <BrushRadius
value={brushRadius} value={state.brushRadius}
onChange={updateBrushSize} onChange={updateBrushSize}
min={configuration.minRadius} min={configuration.minRadius}
max={configuration.maxRadius} max={configuration.maxRadius}
/> />
</form> </form>
)} )}
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setState(state => ({ ...state, showSettings: true }))}
/>
<h3>Segmentations</h3> <h3>Segmentations</h3>
<div className="segmentations"> <div className="segmentations">
<SegmentationSelect <SegmentationSelect
value={state.labelmapList.find( value={selectedSegmentationOption}
i => i.value === state.selectedSegmentation
)}
formatOptionLabel={SegmentationItem} formatOptionLabel={SegmentationItem}
options={state.labelmapList} options={state.labelMapList}
/> />
</div> </div>
<ScrollableArea> <SegmentsSection
<TableList count={state.segmentList.length}
customHeader={<SegmentsHeader count={state.segmentList.length} />} isVisible={
state.segmentsHidden.filter(isHidden => isHidden === true).length <
state.segmentNumbers.length
}
onVisibilityChange={onVisibilityChangeHandler}
> >
{state.segmentList} <ScrollableArea>
</TableList> <TableList headless>{state.segmentList}</TableList>
</ScrollableArea> </ScrollableArea>
</SegmentsSection>
</div> </div>
); );
} }
@ -613,7 +691,7 @@ SegmentationPanel.propTypes = {
SegmentationPanel.defaultProps = {}; 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} StudyInstanceUID
* @param {string} SeriesInstanceUID * @param {string} SeriesInstanceUID
@ -637,56 +715,48 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
return referencedDisplaysets; return referencedDisplaysets;
}; };
/** const SegmentsSection = ({
* count,
* @param {*} firstImageId children,
* @param {*} activeSegmentIndex isVisible: defaultVisibility,
* @returns onVisibilityChange,
*/ }) => {
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex, activeViewport) => { const [isVisible, setIsVisible] = useState(defaultVisibility);
if (segmentIndex === activeSegmentIndex) {
log.info(`${activeSegmentIndex} is already the active segment`);
return;
}
const { state } = cornerstoneTools.getModule('segmentation'); const onVisibilityChangeHandler = () => {
const brushStackState = state.series[firstImageId]; const newVisibility = !isVisible;
setIsVisible(newVisibility);
const labelmap3D = onVisibilityChange(newVisibility);
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
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 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 }) => { useEffect(() => {
setIsVisible(defaultVisibility);
}, [defaultVisibility]);
return ( return (
<React.Fragment> <div className="SegmentsSection">
<div className="tableListHeaderTitle">Segments</div> <div className="header">
<div className="numberOfItems">{count}</div> <div>Segments</div>
</React.Fragment> <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>
); );
}; };
const noop = () => {};
SegmentsSection.defaultProps = {
onVisibilityChange: noop,
};
export default SegmentationPanel; export default SegmentationPanel;

View File

@ -55,6 +55,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
referencedDisplaySetUID: null, // Assigned when loaded. referencedDisplaySetUID: null, // Assigned when loaded.
labelmapIndex: null, // Assigned when loaded. labelmapIndex: null, // Assigned when loaded.
isLoaded: false, isLoaded: false,
hasOverlapping: false,
SeriesDate, SeriesDate,
SeriesTime, SeriesTime,
SeriesNumber, SeriesNumber,
@ -81,7 +82,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
StudyInstanceUID, StudyInstanceUID,
referencedDisplaySet.SeriesInstanceUID referencedDisplaySet.SeriesInstanceUID
); );
return new Promise((resolve, reject) => { return new Promise(async (resolve, reject) => {
let results; let results;
try { try {
results = _parseSeg(segArrayBuffer, imageIds); results = _parseSeg(segArrayBuffer, imageIds);
@ -90,18 +91,47 @@ export default function getSopClassHandlerModule({ servicesManager }) {
segDisplaySet.loadError = true; segDisplaySet.loadError = true;
reject(error); reject(error);
} }
const { labelmapBufferArray, segMetadata, segmentsOnFrame, segmentsOnFrameArray } = results; const {
labelmapBufferArray,
segMetadata,
segmentsOnFrame,
segmentsOnFrameArray,
} = results;
let labelmapIndex;
if (labelmapBufferArray.length > 1) { if (labelmapBufferArray.length > 1) {
let labelmapIndexes = [];
for (let i = 0; i < labelmapBufferArray.length; ++i) { 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 { } else {
loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[0], segMetadata, segmentsOnFrame, []); labelmapIndex = await loadSegmentation(
imageIds,
segDisplaySet,
labelmapBufferArray[0],
segMetadata,
segmentsOnFrame,
[]
);
} }
segDisplaySet.labelmapIndex = 0; resolve(labelmapIndex);
resolve();
}); });
}; };

View File

@ -34,7 +34,11 @@ export default async function loadSegmentation(
* Cache each labelmap segments. * Cache each labelmap segments.
* This data is used to determine the active label map when a given segment is activated/clicked. * 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; segDisplaySet.labelmapIndex = labelmapIndex;
/* /*
@ -57,6 +61,8 @@ export default async function loadSegmentation(
console.log('Segmentation loaded.'); console.log('Segmentation loaded.');
const event = new CustomEvent('extensiondicomsegmentationsegloaded'); const event = new CustomEvent('extensiondicomsegmentationsegloaded');
document.dispatchEvent(event); document.dispatchEvent(event);
return labelmapIndex;
} }
function _getNextLabelmapIndex(firstImageId) { function _getNextLabelmapIndex(firstImageId) {

View File

@ -37,9 +37,14 @@ export default async function setActiveLabelmap(
? brushStackState.activeLabelmapIndex ? brushStackState.activeLabelmapIndex
: undefined; : 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`); log.warn(`${activeLabelmapIndex} is already the active labelmap`);
return displaySet.labelmapIndex; return labelmapIndex;
} }
if (!displaySet.isLoaded) { if (!displaySet.isLoaded) {
@ -55,13 +60,18 @@ export default async function setActiveLabelmap(
await loadPromise; await loadPromise;
} }
labelmapIndex =
displaySet.hasOverlapping === true
? displaySet.originLabelMapIndex
: displaySet.labelmapIndex;
// This might have just been created, so need to use the non-cached value. // This might have just been created, so need to use the non-cached value.
state = cornerstoneTools.getModule('segmentation').state; state = cornerstoneTools.getModule('segmentation').state;
brushStackState = state.series[firstImageId]; brushStackState = state.series[firstImageId];
brushStackState.activeLabelmapIndex = displaySet.labelmapIndex; brushStackState.activeLabelmapIndex = labelmapIndex;
refreshViewports(); refreshViewports();
callback(); callback();
return displaySet.labelmapIndex; return labelmapIndex;
} }

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 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) ## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.0...@ohif/extension-vtk@1.10.1) (2020-12-10)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.10.1", "version": "1.10.2",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@ -53,8 +53,8 @@
"react-vtkjs-viewport": "^0.14.2" "react-vtkjs-viewport": "^0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.12.0", "@ohif/core": "^2.12.1",
"@ohif/ui": "^1.8.0", "@ohif/ui": "^1.8.1",
"cornerstone-tools": "^4.20.1", "cornerstone-tools": "^4.20.1",
"cornerstone-wado-image-loader": "^3.1.0", "cornerstone-wado-image-loader": "^3.1.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",

View File

@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component {
SOPInstanceUID, SOPInstanceUID,
frameIndex frameIndex
) => { ) => {
const { UINotificationService } = this.props.servicesManager.services;
const stack = OHIFVTKViewport.getCornerstoneStack( const stack = OHIFVTKViewport.getCornerstoneStack(
studies, studies,
StudyInstanceUID, StudyInstanceUID,
@ -159,11 +161,14 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState; const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex]; const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
if (brushStackState.labelmaps3D.length > 1 && this.props.viewportIndex === 0) { if (
const { UINotificationService } = this.props.servicesManager.services; brushStackState.labelmaps3D.length > 1 &&
this.props.viewportIndex === 0
) {
UINotificationService.show({ UINotificationService.show({
title: 'Overlapping Segmentation Found', 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', type: 'info',
}); });
} }
@ -305,6 +310,7 @@ class OHIFVTKViewport extends Component {
seriesDescription: displaySet.seriesDescription, seriesDescription: displaySet.seriesDescription,
}; };
try {
const { const {
imageDataObject, imageDataObject,
labelmapDataObject, labelmapDataObject,
@ -351,6 +357,32 @@ class OHIFVTKViewport extends Component {
}, 200); }, 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() { componentDidMount() {

View File

@ -9,7 +9,9 @@ import { version } from '../package.json';
// import loadLocales from './loadLocales'; // import loadLocales from './loadLocales';
const OHIFVTKViewport = asyncComponent(() => const OHIFVTKViewport = asyncComponent(() =>
retryImport(() => import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')) retryImport(() =>
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
)
); );
const vtkExtension = { const vtkExtension = {
@ -21,7 +23,11 @@ const vtkExtension = {
getViewportModule({ commandsManager, servicesManager }) { getViewportModule({ commandsManager, servicesManager }) {
const ExtendedVTKViewport = props => ( const ExtendedVTKViewport = props => (
<OHIFVTKViewport {...props} servicesManager={servicesManager} /> <OHIFVTKViewport
{...props}
servicesManager={servicesManager}
commandsManager={commandsManager}
/>
); );
return withCommandsManager(ExtendedVTKViewport, commandsManager); return withCommandsManager(ExtendedVTKViewport, commandsManager);
}, },

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 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) # [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.12.0", "version": "2.12.1",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",

View File

@ -8,6 +8,7 @@
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight" * @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success" * @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true] * @property {boolean} [autoClose=true]
* @property {object} [action=null]
*/ */
const name = 'UINotificationService'; const name = 'UINotificationService';
@ -34,7 +35,7 @@ const serviceImplementation = {
* Create and show a new UI notification; returns the * Create and show a new UI notification; returns the
* ID of the created notification. * 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 * @returns {number} id
*/ */
function _show({ function _show({
@ -44,6 +45,7 @@ function _show({
position = 'bottomRight', position = 'bottomRight',
type = 'info', type = 'info',
autoClose = true, autoClose = true,
action = null,
}) { }) {
return serviceImplementation._show({ return serviceImplementation._show({
title, title,
@ -52,6 +54,7 @@ function _show({
position, position,
type, type,
autoClose, autoClose,
action,
}); });
} }

View File

@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 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) # [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.8.0", "version": "1.8.1",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",

View File

@ -156,19 +156,50 @@
transition: all 300ms ease; 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); background-color: var(--snackbar-success);
} }
.sb-error { .sb-error,
.sb-error .sb-action {
background-color: var(--snackbar-error); background-color: var(--snackbar-error);
} }
.sb-warning { .sb-warning,
.sb-warning .sb-action {
background-color: var(--snackbar-warning); background-color: var(--snackbar-warning);
} }
.sb-info { .sb-info,
.sb-info .sb-action {
background-color: var(--snackbar-info); background-color: var(--snackbar-info);
} }

View File

@ -1,10 +1,14 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => { const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => { const handleClose = () => {
onClose(options.id); onClose(options.id);
}; };
const handleClick = () => {
options.action.onClick({ ...options, close: handleClose });
};
useEffect(() => { useEffect(() => {
if (options.autoClose) { if (options.autoClose) {
setTimeout(() => { setTimeout(() => {
@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => {
</span> </span>
{options.title && <div className="sb-title">{options.title}</div>} {options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>} {options.message && <div className="sb-message">{options.message}</div>}
{options.action && (
<button className="sb-action" onClick={handleClick}>
{options.action.label}
</button>
)}
</div> </div>
); );
}; };

View File

@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => {
autoClose: true, autoClose: true,
position: 'bottomRight', position: 'bottomRight',
type: SnackbarTypes.INFO, type: SnackbarTypes.INFO,
action: null,
}; };
const [count, setCount] = useState(1); const [count, setCount] = useState(1);

View File

@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 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) ## [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 **Note:** Version bump only for package @ohif/viewer

View File

@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "4.8.1", "version": "4.8.4",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@ -49,18 +49,18 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^2.12.0", "@ohif/core": "^2.12.1",
"@ohif/extension-cornerstone": "^2.10.0", "@ohif/extension-cornerstone": "^2.10.0",
"@ohif/extension-debugging": "^0.1.6", "@ohif/extension-debugging": "^0.1.6",
"@ohif/extension-dicom-html": "^1.3.0", "@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-pdf": "^1.0.6",
"@ohif/extension-dicom-rt": "^0.6.1", "@ohif/extension-dicom-rt": "^0.6.2",
"@ohif/extension-dicom-segmentation": "^0.5.0", "@ohif/extension-dicom-segmentation": "^0.5.1",
"@ohif/extension-lesion-tracker": "^0.2.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/i18n": "^0.53.0",
"@ohif/ui": "^1.8.0", "@ohif/ui": "^1.8.1",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",