Support of RTSTRUCT visualization (#1524)
* 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. * WIP consume RTSTRUCT * Doc API and add endpoints. * Rebase on SEG PR. * Update DICOM Keyword variables. * Fix metadata vars format * Add basic RT panel along with configuration * Wire up configuration with settings component * Mock structure set tables * Improve design of settings * Integrate list of contours * Extract panel section * Filter by seriesuid * Fix slow settings * Add jump to image * Fix expandable items * Improvements and CR update * CR Update: Remove google config * CR Update: Remove erase tool test * CR Update: Remove instance * CR Update: Fix issue with undefined prop * CR Update: Update yarn lock * Hide actions of structure item 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:
39 files changed
+4770
-2770
No files matched your search
@@ -0,0 +1,309 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import * as dcmjs from 'dcmjs';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
|
||||
import TOOL_NAMES from './utils/toolNames';
|
||||
|
||||
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
|
||||
const globalImageIdSpecificToolStateManager =
|
||||
cornerstoneTools.globalImageIdSpecificToolStateManager;
|
||||
const { DicomLoaderService } = OHIF.utils;
|
||||
|
||||
export default async function loadRTStruct(
|
||||
rtStructDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
) {
|
||||
const rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||
|
||||
// Set here is loading is asynchronous.
|
||||
// If this function throws its set back to false.
|
||||
rtStructDisplaySet.isLoaded = true;
|
||||
|
||||
const { StudyInstanceUID, SeriesInstanceUID } = referencedDisplaySet;
|
||||
|
||||
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
rtStructDisplaySet,
|
||||
studies
|
||||
);
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
|
||||
const rtStructDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
);
|
||||
|
||||
rtStructDataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
|
||||
dicomData.meta
|
||||
);
|
||||
|
||||
// global cornerstone tools state to attach measurements to.
|
||||
const toolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
const {
|
||||
StructureSetROISequence,
|
||||
ROIContourSequence,
|
||||
RTROIObservationsSequence,
|
||||
StructureSetLabel,
|
||||
} = rtStructDataset;
|
||||
|
||||
// Define our structure set entry and add it to the rtstruct module state.
|
||||
const structureSet = {
|
||||
StructureSetLabel,
|
||||
SeriesInstanceUID: rtStructDataset.SeriesInstanceUID,
|
||||
ROIContours: [],
|
||||
referencedSeriesSequence:
|
||||
rtStructDisplaySet.metadata.ReferencedSeriesSequence,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
rtStructModule.setters.structureSet(structureSet);
|
||||
|
||||
const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
);
|
||||
|
||||
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
||||
|
||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||
|
||||
const ROIContour = ROIContourSequence[i];
|
||||
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
||||
|
||||
if (!ContourSequence) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour
|
||||
);
|
||||
|
||||
for (let c = 0; c < ContourSequence.length; c++) {
|
||||
const {
|
||||
ContourImageSequence,
|
||||
ContourData,
|
||||
NumberOfContourPoints,
|
||||
ContourGeometricType,
|
||||
} = ContourSequence[c];
|
||||
|
||||
if (ContourGeometricType !== 'CLOSED_PLANAR') {
|
||||
// TODO: Do we want to visualise types other than closed planar?
|
||||
// We could easily do open planar.
|
||||
continue;
|
||||
}
|
||||
|
||||
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
|
||||
toolState,
|
||||
imageId,
|
||||
rtStructDisplayToolName
|
||||
);
|
||||
|
||||
const imagePlane = cornerstone.metaData.get('imagePlaneModule', imageId);
|
||||
const points = [];
|
||||
|
||||
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
|
||||
points.push({
|
||||
x: ContourData[p],
|
||||
y: ContourData[p + 1],
|
||||
z: ContourData[p + 2],
|
||||
});
|
||||
}
|
||||
|
||||
transformPointsToImagePlane(points, imagePlane);
|
||||
|
||||
const measurementData = {
|
||||
handles: {
|
||||
points,
|
||||
},
|
||||
structureSetSeriesInstanceUid: rtStructDataset.SeriesInstanceUID,
|
||||
ROINumber: ReferencedROINumber,
|
||||
};
|
||||
|
||||
imageIdSpecificToolData.push(measurementData);
|
||||
}
|
||||
}
|
||||
|
||||
_setToolEnabledIfNotEnabled(rtStructDisplayToolName);
|
||||
|
||||
/*
|
||||
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
|
||||
*
|
||||
* Currently we are using a non-ideal implementation through a custom event to notify the rtstruct panel
|
||||
* or other components that could rely on loaded rtstructs that
|
||||
* the first batch of structs were loaded so that e.g. when the user opens the panel
|
||||
* before the structs are fully loaded, the panel can subscribe to this custom event
|
||||
* and update itself with the new structs.
|
||||
*
|
||||
* This limitation is due to the fact that the rtmodule is an object (which will be
|
||||
* updated after the structs are loaded) that React its not aware of its changes
|
||||
* because the module object its not passed in to the panel component as prop but accessed externally.
|
||||
*
|
||||
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
||||
* allows us to easily watch the module or the rtstruct loading process in any other component
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
const event = new CustomEvent('extensiondicomrtrtloaded');
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function _setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour
|
||||
) {
|
||||
const StructureSetROI = StructureSetROISequence.find(
|
||||
structureSetROI =>
|
||||
structureSetROI.ROINumber === ROIContour.ReferencedROINumber
|
||||
);
|
||||
|
||||
const ROIContourData = {
|
||||
ROINumber: StructureSetROI.ROINumber,
|
||||
ROIName: StructureSetROI.ROIName,
|
||||
ROIGenerationAlgorithm: StructureSetROI.ROIGenerationAlgorithm,
|
||||
ROIDescription: StructureSetROI.ROIDescription,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
_setROIContourDataColor(ROIContour, ROIContourData);
|
||||
|
||||
if (RTROIObservationsSequence) {
|
||||
// If present, add additional RTROIObservations metadata.
|
||||
_setROIContourRTROIObservations(
|
||||
ROIContourData,
|
||||
RTROIObservationsSequence,
|
||||
ROIContour.ReferencedROINumber
|
||||
);
|
||||
}
|
||||
|
||||
structureSet.ROIContours.push(ROIContourData);
|
||||
}
|
||||
|
||||
function _setROIContourDataColor(ROIContour, ROIContourData) {
|
||||
let { ROIDisplayColor, RecommendedDisplayCIELabValue } = ROIContour;
|
||||
|
||||
if (!ROIDisplayColor && RecommendedDisplayCIELabValue) {
|
||||
// If ROIDisplayColor is absent, try using the RecommendedDisplayCIELabValue color.
|
||||
ROIDisplayColor = dicomlab2RGB(RecommendedDisplayCIELabValue);
|
||||
}
|
||||
|
||||
if (ROIDisplayColor) {
|
||||
ROIContourData.colorArray = [...ROIDisplayColor];
|
||||
} else {
|
||||
//Choose a color from the cornerstoneTools colorLUT
|
||||
// We sample from the default color LUT here (i.e. 0), as we have nothing else to go on.
|
||||
const { getters } = cornerstoneTools.getModule('segmentation');
|
||||
const color = getters.colorForSegmentIndexColorLUT(
|
||||
0,
|
||||
ROIContourData.ROINumber
|
||||
);
|
||||
|
||||
ROIContourData.colorArray = [...color];
|
||||
}
|
||||
}
|
||||
|
||||
function _setROIContourRTROIObservations(
|
||||
ROIContourData,
|
||||
RTROIObservationsSequence,
|
||||
ROINumber
|
||||
) {
|
||||
const RTROIObservations = RTROIObservationsSequence.find(
|
||||
RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber
|
||||
);
|
||||
|
||||
if (RTROIObservations) {
|
||||
// Deep copy so we don't keep the reference to the dcmjs dataset entry.
|
||||
const {
|
||||
ObservationNumber,
|
||||
ROIObservationDescription,
|
||||
RTROIInterpretedType,
|
||||
ROIInterpreter,
|
||||
} = RTROIObservations;
|
||||
|
||||
ROIContourData.RTROIObservations = {
|
||||
ObservationNumber,
|
||||
ROIObservationDescription,
|
||||
RTROIInterpretedType,
|
||||
ROIInterpreter,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function _setToolEnabledIfNotEnabled(toolName) {
|
||||
cornerstone.getEnabledElements().forEach(enabledElement => {
|
||||
const { element } = enabledElement;
|
||||
const tool = cornerstoneTools.getToolForElement(element, toolName);
|
||||
|
||||
if (tool.mode !== 'enabled') {
|
||||
// If not already active or passive, set passive so contours render.
|
||||
cornerstoneTools.setToolEnabled(toolName);
|
||||
}
|
||||
|
||||
cornerstone.updateImage(element);
|
||||
});
|
||||
}
|
||||
|
||||
function _getOrCreateImageIdSpecificToolData(toolState, imageId, toolName) {
|
||||
if (toolState.hasOwnProperty(imageId) === false) {
|
||||
toolState[imageId] = {};
|
||||
}
|
||||
|
||||
const imageIdToolState = toolState[imageId];
|
||||
|
||||
// If we don't have tool state for this type of tool, add an empty object
|
||||
if (imageIdToolState.hasOwnProperty(toolName) === false) {
|
||||
imageIdToolState[toolName] = {
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
return imageIdToolState[toolName].data;
|
||||
}
|
||||
|
||||
const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
||||
const imageIdSopInstanceUidPairsEntry = imageIdSopInstanceUidPairs.find(
|
||||
imageIdSopInstanceUidPairsEntry =>
|
||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||
);
|
||||
|
||||
return imageIdSopInstanceUidPairsEntry.imageId;
|
||||
};
|
||||
|
||||
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = study.displaySets.filter(set => {
|
||||
return set.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
// TODO -> We could make check the instance list and see if any match?
|
||||
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
|
||||
// ^ Will that even happen?
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySets[0];
|
||||
|
||||
return referencedDisplaySet.images.map(image => {
|
||||
return {
|
||||
imageId: image.getImageId(),
|
||||
sopInstanceUID: image.getSOPInstanceUID(),
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user