Merge branch 'master' of github.com:OHIF/Viewers into IDC-1136

This commit is contained in:
igoroctaviano 2021-01-21 11:55:17 -03:00
commit d1e52dd6a6
23 changed files with 758 additions and 496 deletions

View File

@ -62,11 +62,24 @@ class DicomMicroscopyViewport extends Component {
);
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
this.viewer = new microscopyViewer({
client: dicomWebClient,
metadata,
retrieveRendered: false,
});
try {
this.viewer = new microscopyViewer({
client: dicomWebClient,
metadata,
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 });
});
@ -92,8 +105,8 @@ class DicomMicroscopyViewport extends Component {
{this.state.error ? (
<h2>{JSON.stringify(this.state.error)}</h2>
) : (
<div style={style} ref={this.container} />
)}
<div style={style} ref={this.container} />
)}
</div>
);
}

View File

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

View File

@ -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.
## [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)

View File

@ -1,6 +1,6 @@
{
"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.",
"author": "OHIF",
"license": "MIT",

View File

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

View File

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

View File

@ -44,7 +44,7 @@ const RTPanel = ({
activeContexts = [],
contexts = {},
activeViewport,
getActiveViewport
getActiveViewport,
}) => {
const isVTK = () => activeContexts.includes(contexts.VTK);
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
@ -55,7 +55,7 @@ const RTPanel = ({
referencedDisplaysets: [],
sets: [],
selectedSet: null,
isLocked: false
isLocked: false,
};
const [state, setState] = useState(DEFAULT_STATE);
@ -84,12 +84,17 @@ const RTPanel = ({
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => {
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
document.removeEventListener(
'extensiondicomrtrtloaded',
updateStructureSets
);
};
}, []);
useEffect(() => {
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
@ -235,7 +240,7 @@ const RTPanel = ({
return (
<div className="dcmrt-panel">
<div className="dcmrt-panel-header">
<h3>RT Structure Sets</h3>
{' '}
{!state.isLocked && (
<Icon
className="cog-icon"
@ -245,71 +250,75 @@ const RTPanel = ({
onClick={() => setShowSettings(true)}
/>
)}
<h3>RT Structure Sets</h3>
</div>
{!state.isLocked && !state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
{state.sets && state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
{!state.isLocked && !state.referencedDisplaysets.length && (
<LoadingIndicator expand height="70px" width="70px" />
)}
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
if (!loadedSet) return null;
if (!loadedSet) return null;
return (
<PanelSection
key={SeriesInstanceUID}
title={metadata.StructureSetLabel}
loading={!isLoaded || !loadedSet}
visible={isLoaded && loadedSet && loadedSet.visible}
hideVisibleButton={!isLoaded}
expanded={
isLoaded &&
loadedSet.SeriesInstanceUID ===
state.selectedSet.SeriesInstanceUID
}
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
if (newVisibility) {
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
} else {
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
return (
<PanelSection
key={SeriesInstanceUID}
title={metadata.StructureSetLabel}
loading={!isLoaded || !loadedSet}
visible={isLoaded && loadedSet && loadedSet.visible}
hideVisibleButton={!isLoaded}
expanded={
isLoaded &&
loadedSet.SeriesInstanceUID ===
state.selectedSet.SeriesInstanceUID
}
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(activeViewport, studies);
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
if (newVisibility) {
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
} else {
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
}
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
const selectedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
setState(state => ({ ...state, selectedSet, sets }));
}
}}
>
<ScrollableArea>
<TableList headless>
{isLoaded &&
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
setState(state => ({ ...state, sets }));
refreshViewport();
}}
onExpandChange={async () => {
if (!isLoaded) {
await displaySet.load(activeViewport, studies);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
activeViewport.SeriesInstanceUID
);
const selectedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
setState(state => ({ ...state, selectedSet, sets }));
}
}}
>
<ScrollableArea>
<TableList headless>
{isLoaded &&
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
</div>
);
};

View File

@ -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.
## [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)

View File

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

View File

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

View File

@ -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;
}

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 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(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={() => 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(
<SegmentItem
key={segmentNumber}
itemClass={`segment-item ${sameSegment && 'selected'}`}
onClick={setCurrentSelectedSegment}
label={segmentLabel}
index={segmentNumber}
color={color}
visible={!labelmap3D.segmentsHidden[segmentIndex]}
onVisibilityChange={onSegmentVisibilityChangeHandler}
/>
);
}
return {
items: segmentList,
numbers: segmentNumbers,
segmentsHidden: labelmap3D.segmentsHidden,
};
/*
* Let's iterate over segmentIndexes ^ above
* If meta has a match, use it to show info
* If now, add "no-meta" class
* Show default name
*/
};
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 (
<SegmentationSettings
disabledFields={isVTK() ? disabledConfigurationFields : []}
configuration={configuration}
onBack={() => updateState('showSegmentationSettings', false)}
onBack={() => setState(state => ({ ...state, showSettings: false }))}
onChange={updateConfiguration}
/>
);
} else {
return (
<div
className={`dcmseg-segmentation-panel ${state.isDisabled &&
'disabled'}`}
className={classNames('dcmseg-segmentation-panel', {
disabled: state.isDisabled,
})}
>
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => updateState('showSegmentationSettings', true)}
/>
{false && (
<form className="selector-form">
<BrushColorSelector
defaultColor={brushColor}
defaultColor={state.brushColor}
index={state.selectedSegment}
onNext={incrementSegment}
onPrev={decrementSegment}
/>
<BrushRadius
value={brushRadius}
value={state.brushRadius}
onChange={updateBrushSize}
min={configuration.minRadius}
max={configuration.maxRadius}
/>
</form>
)}
<Icon
className="cog-icon"
name="cog"
width="25px"
height="25px"
onClick={() => setState(state => ({ ...state, showSettings: true }))}
/>
<h3>Segmentations</h3>
<div className="segmentations">
<SegmentationSelect
value={state.labelmapList.find(
i => i.value === state.selectedSegmentation
)}
value={selectedSegmentationOption}
formatOptionLabel={SegmentationItem}
options={state.labelmapList}
options={state.labelMapList}
/>
</div>
<ScrollableArea>
<TableList
customHeader={<SegmentsHeader count={state.segmentList.length} />}
>
{state.segmentList}
</TableList>
</ScrollableArea>
<SegmentsSection
count={state.segmentList.length}
isVisible={
state.segmentsHidden.filter(isHidden => isHidden === true).length <
state.segmentNumbers.length
}
onVisibilityChange={onVisibilityChangeHandler}
>
<ScrollableArea>
<TableList headless>{state.segmentList}</TableList>
</ScrollableArea>
</SegmentsSection>
</div>
);
}
@ -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 (
<React.Fragment>
<div className="tableListHeaderTitle">Segments</div>
<div className="numberOfItems">{count}</div>
</React.Fragment>
<div className="SegmentsSection">
<div className="header">
<div>Segments</div>
<div className="icons">
<Icon
className={`eye-icon ${isVisible && 'expanded'}`}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={onVisibilityChangeHandler}
/>
<div className="count">{count}</div>
</div>
</div>
{children}
</div>
);
};
const noop = () => {};
SegmentsSection.defaultProps = {
onVisibilityChange: noop,
};
export default SegmentationPanel;

View File

@ -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);
});
};

View File

@ -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) {

View File

@ -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;
}

View File

@ -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)

View File

@ -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",

View File

@ -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)

View File

@ -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",

View File

@ -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)

View File

@ -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",

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "4.8.1",
"version": "4.8.3",
"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-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",