ohif-viewer/extensions/dicom-rt/src/loadRTStruct.js

309 lines
9.2 KiB
JavaScript
Raw Normal View History

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>
2020-04-09 12:21:43 +02:00
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
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>
2020-04-09 12:21:43 +02:00
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(),
};
});
}