* 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:
78 files changed
+3071
-1962
No files matched your search
@@ -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;
|
||||
Reference in new issue
Block a user