feat: Segmentations Settings UI - Phase 1 #1391 (#1392)

* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Filter segs and fetch correct metadata.

* Initiate seg loading logic on series load into viewport. Need to write.

* Automatic consumption of SEG functional.

* Pull in master.

* Fix issues after pulling in master.

* Clean up old segmentation handlers.

* WIP

* WIP

* Poppulate viewport with metadata.

* Show hide/segmentation in react-vtkjs-viewport.

* feat: 🎸 View segmentations in vtkjs viewport

* Rebase on master.

* format format format

* add basic extension parts

* Updated yarn lock

* Fetch derived/referenced displaysets in sidepanel

* Add buttons, expose seriesDescription, write notes for Danny.

* Add growcut tool extention. -> Move to a seperate repo.

* Change some text fields.

* expose dialog provider

* hide draggable area

* No pointer events for now; need to revisit

* Don't filter seg from the outset for now

* fiddling about

* fix path to series data

* fix logic to determine next available labelmapindex

* Ability to swtich between labelmaps

* Return a promise so we can make sure our labelmap is loaded before pulling metadata

* Better UI to toggle labelmaps

* Fix accidental untoggle of active labelmap for ui

* Remove brush tool from cornerstone extension

* Add cstools in preInit for seg extension

* Add buttons for seg magic

* Hide the measurements panel for now

* Slightly better seg/scissor icons

* Add eraser

* ability to switch segments

* seg color / switcher

* Fix vtk labelmap cache.

* sidepanel should scroll if there's overflow

* Use a fancier range slider

* Slightly prettier buttons

* Push in progress

* A bit more reactive; handle no-meta

* Update to vtk.js 11.14.0 and react-vtkjs-viewport 0.6.0

* chore: kickstart preview

* Fix conflict issues

* Refactor panel

* Isolate increment functions

* Wip segment table

* Hide selector and add scroll to segmentation

* Increase width of segment description

* Hide not used items

* Add new select for segmentations

* Align ui

* Fix default segmentation

* Fix broken import test

* Get measurements panel back and add spacing to grouped buttons

* Hide brush and selector and refresh when switching segmentations

* Add pointer events back and fix broken tests for measurement table

* Update segment list when switching humbnails

* CR Update: Minor UI adjustments

* CR Update: Set active segment

* Move components to components folder

* Add new settings ui

* Integrate configuration

* Improve styles

* Float numbers

* Improve css

* Refresh settings menu if side panel changed

* Fix settings rendering logic and remove comments

* Extract components

* Remove not used tools

* Remove non used extension

* Use style variables and remove comments

* Remove non used dep from lock

* Remove tools

* Remove segmentation from toolbar

* Add todo and brushradius from config

* Fix packagejson growcut import

* Remove webpack config already defined in parent

* Update package json description

* Change component name to british english to us

* Improve prop types

* Rename cornerstone label map event

* Scope css

* Extract and add doc to helper

* Update jsdocs for getDerivedDatasets

* Add proptypes and cr updates

* Update tablistitem proptypes

* config is readonly, rollback destructuring

* Remove event for side panel

* Add jsdocs for util

* Extract getimageid to studymetada

* Use ohif log instead of console

* Remove specific style from side panel

* Fix default props for brush radius

* Descructure configuration props from settings component to avoid invalid props

* Update proptypes to be required (settings)

* Update readme

* Remove commandsmodule

* Get configuration module inside the component

* Set selected segment

* Rename extension and use modality to filter

* Fix for failing tests (#1423)

* hello world

* Finish rebase for Igor.

* Re-fix cornerstoneTools version that got messed up in merge.

* Fix reactivity of isDisabled, write implementation for the SEG panel.

* Nuke yarn.lock to stack CST to 4.12.0

* Remove debugger statements.

* fix: bump cornerstone-tools version to address toFixed issue

* test: force tests for MISTER^MR to navigate to study directly

* test: remove eraser tool test

* test: count is off by one

* test: don't save SR state for the time being

* test: disable more tests

Co-authored-by: James A. Petts <jamesapetts@gmail.com>
Co-authored-by: dannyrb <danny.ri.brown@gmail.com>
Co-authored-by: igoroctaviano] <igoroctaviano@users.noreply.github.com>
Co-authored-by: Mirna Silva <mirna.silva@radicalimaging.com>
This commit is contained in:
authored and GitHub committed 2020-03-13 15:45:26 -04:00
1 parent d426b78967
commit e8842cf8ae
78 files changed
+3071 -1962

No files matched your search

+100 -50
View File
@@ -5,33 +5,30 @@ import LoadingIndicator from './LoadingIndicator.js';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core';
import handleSegmentationStorage from './handleSegmentationStorage.js';
import cornerstoneTools from 'cornerstone-tools';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';
const segmentationModule = cornerstoneTools.getModule('segmentation');
const { StackManager } = OHIF.utils;
const style = { width: '100%', height: '100%', position: 'relative' };
const SOP_CLASSES = {
SEGMENTATION_STORAGE: '1.2.840.10008.5.1.4.1.1.66.4',
};
const specialCaseHandlers = {};
specialCaseHandlers[
SOP_CLASSES.SEGMENTATION_STORAGE
] = handleSegmentationStorage;
// TODO: Figure out where we plan to put this long term
const volumeCache = {};
const labelmapCache = {};
/**
* Create a labelmap image with the same dimensions as our background volume.
*
* @param backgroundImageData vtkImageData
*/
/* TODO: Not currently used until we have drawing tools in vtkjs.
function createLabelMapImageData(backgroundImageData) {
// TODO => Need to do something like this if we start drawing a new segmentation
// On a vtkjs viewport.
const labelMapData = vtkImageData.newInstance(
backgroundImageData.get('spacing', 'origin', 'direction')
);
@@ -46,7 +43,7 @@ function createLabelMapImageData(backgroundImageData) {
labelMapData.getPointData().setScalars(dataArray);
return labelMapData;
}
} */
class OHIFVTKViewport extends Component {
state = {
@@ -147,33 +144,58 @@ class OHIFVTKViewport extends Component {
frameIndex
);
let imageDataObject;
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
let labelmapDataObject;
let labelmapColorLUT;
switch (SOPClassUID) {
case SOP_CLASSES.SEGMENTATION_STORAGE:
throw new Error('Not yet implemented');
/*
const data = handleSegmentationStorage(
stack.imageIds,
displaySetInstanceUID
const firstImageId = stack.imageIds[0];
const { state } = segmentationModule;
const brushStackState = state.series[firstImageId];
if (brushStackState) {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
if (labelmapCache[vtkLabelmapID]) {
labelmapDataObject = labelmapCache[vtkLabelmapID];
} else {
// TODO -> We need an imageId based getter in cornerstoneTools
const labelmapBuffer = labelmap3D.buffer;
// Create VTK Image Data with buffer as input
labelmapDataObject = vtkImageData.newInstance();
const dataArray = vtkDataArray.newInstance({
numberOfComponents: 1, // labelmap with single component
values: new Uint16Array(labelmapBuffer),
});
labelmapDataObject.getPointData().setScalars(dataArray);
labelmapDataObject.setDimensions(...imageDataObject.dimensions);
labelmapDataObject.setSpacing(
...imageDataObject.vtkImageData.getSpacing()
);
labelmapDataObject.setOrigin(
...imageDataObject.vtkImageData.getOrigin()
);
labelmapDataObject.setDirection(
...imageDataObject.vtkImageData.getDirection()
);
imageDataObject = data.referenceDataObject;
labelmapDataObject = data.labelmapDataObject;
// Cache the labelmap volume.
labelmapCache[vtkLabelmapID] = labelmapDataObject;
}
return loadImageData(imageDataObject).then(() => {
return {
data: imageDataObject.vtkImageData,
labelmap: labelmapDataObject,
};
});
*/
default:
imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
return imageDataObject;
labelmapColorLUT = state.colorLutTables[labelmap3D.colorLUTIndex];
}
return {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
};
};
/**
@@ -251,40 +273,48 @@ class OHIFVTKViewport extends Component {
);
}
const SOPClassUID = sopClassUIDs[0];
const imageDataObject = this.getViewportData(
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const dataDetails = {
studyDate: study.studyDate,
studyTime: study.studyTime,
studyDescription: study.studyDescription,
patientName: study.patientName,
patientId: study.patientId,
seriesNumber: String(displaySet.seriesNumber),
seriesDescription: displaySet.seriesDescription,
};
const {
imageDataObject,
labelmapDataObject,
labelmapColorLUT,
} = this.getViewportData(
studies,
StudyInstanceUID,
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex
);
this.imageDataObject = imageDataObject;
// TODO: Temporarily disabling this since it is not yet
// being used and hurts performance significantly.
/*if (!labelmap) {
/* TODO: Not currently used until we have drawing tools in vtkjs.
if (!labelmap) {
labelmap = createLabelMapImageData(data);
}*/
} */
const volumeActor = this.getOrCreateVolume(
imageDataObject,
displaySetInstanceUID
);
this.setState({
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
paintFilterLabelMapImageData: null, // TODO
percentComplete: 0,
});
this.setState(
{
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
paintFilterLabelMapImageData: null, // TODO
percentComplete: 0,
dataDetails,
},
() => {
this.loadProgressively(imageDataObject);
@@ -296,6 +326,9 @@ class OHIFVTKViewport extends Component {
setTimeout(() => {
this.setState({
volumes: [volumeActor],
paintFilterLabelMapImageData: labelmapDataObject,
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
labelmapColorLUT,
});
}, 200);
}
@@ -307,7 +340,7 @@ class OHIFVTKViewport extends Component {
}
componentDidUpdate(prevProps) {
const { studies, displaySet } = this.props.viewportData;
const { displaySet } = this.props.viewportData;
const prevDisplaySet = prevProps.viewportData.displaySet;
if (
@@ -347,12 +380,15 @@ class OHIFVTKViewport extends Component {
});
Promise.all(insertPixelDataPromises).then(() => {
this.setState({ isLoaded: true });
this.setState({
isLoaded: true,
});
});
}
render() {
let childrenWithProps = null;
const { configuration } = segmentationModule;
// TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) {
@@ -367,6 +403,12 @@ class OHIFVTKViewport extends Component {
});
}
const style = { width: '100%', height: '100%', position: 'relative' };
const visible = configuration.renderFill || configuration.renderOutline;
const opacity = configuration.fillAlpha;
const outlineThickness = configuration.outlineThickness;
return (
<>
<div style={style}>
@@ -383,6 +425,14 @@ class OHIFVTKViewport extends Component {
this.state.paintFilterBackgroundImageData
}
viewportIndex={this.props.viewportIndex}
dataDetails={this.state.dataDetails}
labelmapRenderingOptions={{
colorLUT: this.state.labelmapColorLUT,
globalOpacity: opacity,
visible,
outlineThickness,
renderOutline: true,
}}
onScroll={this.props.onScroll}
/>
)}
@@ -1,146 +0,0 @@
import * as dcmjs from 'dcmjs';
import OHIF from '@ohif/core';
import { api } from 'dicomweb-client';
const { StackManager } = OHIF.utils;
function getDisplaySet(studies, StudyInstanceUID, displaySetInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = study.displaySets.find(set => {
return set.displaySetInstanceUID === displaySetInstanceUID;
});
return displaySet;
}
function getDisplaySetsBySeries(studies, StudyInstanceUID, SeriesInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
return study.displaySets.filter(set => {
return set.SeriesInstanceUID === SeriesInstanceUID;
});
}
function getCornerstoneStack(studies, StudyInstanceUID, displaySetInstanceUID) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
// Create shortcut to displaySet
const displaySet = getDisplaySet(
studies,
StudyInstanceUID,
displaySetInstanceUID
);
// Get stack from Stack Manager
const stack = StackManager.findOrCreateStack(study, displaySet);
// Clone the stack here so we don't mutate it later
const stackClone = Object.assign({}, stack);
stackClone.currentImageIdIndex = 0;
return stackClone;
}
function retrieveDicomData(
studyInstanceUID,
seriesInstanceUID,
sopInstanceUid,
wadoRoot
) {
const config = {
url: wadoRoot,
headers: DICOMWeb.getAuthorizationHeader(),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
studyInstanceUID,
seriesInstanceUID,
sopInstanceUid,
};
return dicomWeb.retrieveInstance(options);
}
async function handleSegmentationStorage(
studies,
StudyInstanceUID,
displaySetInstanceUID
) {
const study = studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
const displaySet = getDisplaySet(
studies,
StudyInstanceUID,
displaySetInstanceUID
);
// TODO: This is terrible but we need to use WADO-RS or we can't retrieve the SEG
// from google cloud
const wadoRoot = displaySet.images[0].getData().wadoRoot;
const arrayBuffer = await retrieveDicomData(
displaySet.images[0].getStudyInstanceUID(),
displaySet.images[0].getSeriesInstanceUID(),
displaySet.images[0].getSOPInstanceUID(),
wadoRoot
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
const segments = dcmjs.adapters.VTKjs.Segmentation.generateSegments(dataset);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
const SeriesInstanceUID = dataset.ReferencedSeriesSequence.SeriesInstanceUID;
const displaySets = getDisplaySetsBySeries(
studies,
StudyInstanceUID,
SeriesInstanceUID
);
if (displaySets.length > 1) {
console.warn(
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
);
}
const referenceDisplaySet = displaySets[0];
const imageIds = referenceDisplaySet.images.map(image => image.getImageId());
if (!results) {
throw new Error('Fractional segmentations are not supported');
}
const cachedStack = StackManager.findOrCreateStack(
study,
referenceDisplaySet
);
const stack = Object.assign({}, cachedStack);
stack.currentImageIdIndex = 0;
return {
referenceDataObject,
labelmapDataObject,
};
return {
StudyInstanceUID,
displaySetInstanceUID,
stack,
};
}
export default handleSegmentationStorage;