Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IDC-1672] Highlight SEG segment/ RT structure when click to jump. #2034

Merged
merged 5 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions extensions/debugging/src/DebugReportModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ const getBrowserInfo = () => {
const getSeriesInstanceUIDsPerRow = viewports => {
const { viewportSpecificData, numColumns } = viewports;

debugger;

// NOTE viewportSpecificData is actually an object with numerical keys.
return Object.keys(viewportSpecificData).map(viewportIndex => {
const vsd = viewportSpecificData[viewportIndex];
Expand Down
24 changes: 21 additions & 3 deletions extensions/dicom-rt/src/components/RTPanel/RTPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import StructureSetItem from '../StructureSetItem/StructureSetItem';
import RTPanelSettings from '../RTSettings/RTSettings';
import PanelSection from '../PanelSection/PanelSection';
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
import TOOL_NAMES from '../../utils/toolNames';

const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;

const { studyMetadataManager } = utils;

Expand Down Expand Up @@ -144,13 +147,16 @@ const RTPanel = ({
if (isCornerstone()) {
const enabledElements = cornerstone.getEnabledElements();
const element = enabledElements[activeIndex].element;
const toolState = cornerstoneTools.getToolState(element, 'stack');
const stackToolState = cornerstoneTools.getToolState(
element,
'stack'
);

if (!toolState) {
if (!stackToolState) {
return;
}

const imageIds = toolState.data[0].imageIds;
const imageIds = stackToolState.data[0].imageIds;

const module = cornerstoneTools.getModule('rtstruct');
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
Expand All @@ -159,6 +165,18 @@ const RTPanel = ({
imageIds
);

const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
const imageIdSpecificToolState = toolState[imageId];

const rtstructData =
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];

const specificData = rtstructData.data.find(
rtData => rtData.ROINumber === ROINumber
);

specificData.highlight = true;

const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
Expand Down
59 changes: 45 additions & 14 deletions extensions/dicom-rt/src/tools/RTStructDisplayTool.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';

import TOOL_NAMES from '../utils/toolNames';
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';

// Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw');
Expand Down Expand Up @@ -37,11 +37,17 @@ export default class RTStructDisplayTool extends BaseTool {
return;
}

const { lineWidth, opacity } = rtstructModule.configuration;
const {
lineWidth,
opacity,
highlightOpacity,
} = rtstructModule.configuration;

// We have tool data for this element - iterate over each one and draw it
const context = getNewContext(eventData.canvasContext.canvas);

let crossHairCenter;

for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];

Expand Down Expand Up @@ -75,27 +81,52 @@ export default class RTStructDisplayTool extends BaseTool {
colorArray[2]
},${opacity})`;

// TODO
JamesAPetts marked this conversation as resolved.
Show resolved Hide resolved
let highlight = data.highlight;
const options = { color, lineWidth };

if (highlight) {
crossHairCenter = { x: 0, y: 0 };

points.forEach(point => {
crossHairCenter.x += point.x;
crossHairCenter.y += point.y;
});

crossHairCenter.x /= points.length;
crossHairCenter.y /= points.length;

// TODO: Disabling hightlight for now, it'd be good to bring it back
// when we have a good way of doing this for SEG.

// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
// colorArray[2]
// },${highlightOpacity})`;

// Draw highlight lines.

delete data.highlight; // Don't highlight on next render.
}

switch (data.type) {
case 'CLOSED_PLANAR':
this._renderClosedPlanar(context, eventData.element, points, {
color,
lineWidth,
});
this._renderClosedPlanar(context, eventData.element, points, options);
break;
case 'POINT':
this._renderPoint(context, eventData.element, points, {
color,
lineWidth,
});
this._renderPoint(context, eventData.element, points, options);
break;
case 'OPEN_PLANAR':
this._renderOpenPlanar(context, eventData.element, points, {
color,
lineWidth,
});
this._renderOpenPlanar(context, eventData.element, points, options);
break;
}
}

if (crossHairCenter) {
drawCanvasCrosshairs(eventData, crossHairCenter, {
color: toolColors.getActiveColor(),
lineWidth: 1,
});
}
}

_renderClosedPlanar(context, element, points, options) {
Expand Down
3 changes: 2 additions & 1 deletion extensions/dicom-rt/src/tools/modules/rtStructModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ function refreshViewport() {

const configuration = {
lineWidth: 3,
opacity: 1.0,
opacity: 0.75,
highlightOpacity: 0.5,
};

const state = {
Expand Down
56 changes: 56 additions & 0 deletions extensions/dicom-rt/src/utils/drawCanvasCrosshairs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';

const { importInternal } = cornerstoneTools;
const draw = importInternal('drawing/draw');
const drawLine = importInternal('drawing/drawLine');
const getNewContext = importInternal('drawing/getNewContext');

export default function _drawCanvasCrosshairs(eventData, center, options) {
const context = getNewContext(eventData.canvasContext.canvas);
const { element } = eventData;

const centerCanvas = cornerstone.pixelToCanvas(element, center);

const { clientWidth: width, clientHeight: height } = element;

const offset = 10;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the offset for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is for drawing the crosshairs, i.e. the bit of the crosshair that "isn't drawn". It could be configurable, but its hardcoded for now.


draw(context, context => {
drawLine(
context,
element,
{ x: centerCanvas.x + offset, y: centerCanvas.y },
{ x: width, y: centerCanvas.y },
options,
'canvas'
);

drawLine(
context,
element,
{ x: centerCanvas.x - offset, y: centerCanvas.y },
{ x: 0, y: centerCanvas.y },
options,
'canvas'
);

drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y + offset },
{ x: centerCanvas.x, y: height },
options,
'canvas'
);

drawLine(
context,
element,
{ x: centerCanvas.x, y: centerCanvas.y - offset },
{ x: centerCanvas.x, y: 0 },
options,
'canvas'
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cornerstone from 'cornerstone-core';
import moment from 'moment';
import { utils, log } from '@ohif/core';
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';

import setActiveLabelmap from '../../utils/setActiveLabelMap';
import refreshViewports from '../../utils/refreshViewports';
Expand Down Expand Up @@ -334,6 +335,12 @@ const SegmentationPanel = ({
imageId
);

DICOMSegTempCrosshairsTool.addCrosshair(
element,
imageId,
segmentNumber
);

onSegmentItemClick({
StudyInstanceUID,
SOPInstanceUID,
Expand Down
3 changes: 3 additions & 0 deletions extensions/dicom-segmentation/src/init.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import csTools from 'cornerstone-tools';
import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool';

/**
*
Expand All @@ -17,4 +18,6 @@ export default function init({ servicesManager, configuration = {} }) {
alwaysEraseOnClick: true,
},
});

csTools.addTool(DICOMSegTempCrosshairsTool);
}
127 changes: 127 additions & 0 deletions extensions/dicom-segmentation/src/tools/DICOMSegTempCrosshairsTool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import cornerstoneTools, {
importInternal,
getToolState,
toolColors,
getModule,
globalImageIdSpecificToolStateManager,
} from 'cornerstone-tools';
import cornerstone from 'cornerstone-core';
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
import TOOL_NAMES from './TOOL_NAMES';

const { DICOM_SEG_TEMP_CROSSHAIRS_TOOL } = TOOL_NAMES;
const { getters } = getModule('segmentation');

// Cornerstone 3rd party dev kit imports
const BaseTool = importInternal('base/BaseTool');

/**
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
* @extends cornerstoneTools.BaseTool
*/
export default class DICOMSegTempCrosshairsTool extends BaseTool {
constructor(props = {}) {
const defaultProps = {
mixins: ['enabledOrDisabledBinaryTool'],
name: DICOM_SEG_TEMP_CROSSHAIRS_TOOL,
};

const initialProps = Object.assign(defaultProps, props);

super(initialProps);

this._rtStructModule = cornerstoneTools.getModule('rtstruct');
}

renderToolData(evt) {
const eventData = evt.detail;
const { element } = eventData;
const toolState = getToolState(evt.currentTarget, this.name);

if (!toolState) {
return;
}

// We have tool data for this element - iterate over each one and draw it

for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];
const crossHairCenter = data.center;

drawCanvasCrosshairs(eventData, crossHairCenter, {
color: toolColors.getActiveColor(),
lineWidth: 1,
});

// Remove the crosshairs, we only render them for one redraw.
toolState.data.pop();
}
}
}

DICOMSegTempCrosshairsTool.addCrosshair = (element, imageId, segmentNumber) => {
const labelmap3D = getters.labelmap3D(element);
const stackToolState = cornerstoneTools.getToolState(element, 'stack');
const enabledElement = cornerstone.getEnabledElement(element);

const { rows, columns } = enabledElement.image;

if (!stackToolState) {
return;
}

const imageIds = stackToolState.data[0].imageIds;
const imageIdIndex = imageIds.findIndex(imgId => imgId === imageId);

const labelmap2D = labelmap3D.labelmaps2D[imageIdIndex];
const { pixelData } = labelmap2D;

let xCenter = 0;
let yCenter = 0;

let count = 0;

for (let y = 0; y < rows; y++) {
for (let x = 0; x < columns; x++) {
if (pixelData[y * columns + x] === segmentNumber) {
count++;
xCenter += x + 0.5;
yCenter += y + 0.5;
}
}
}

xCenter /= count;
yCenter /= count;

const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();

if (!globalToolState[imageId]) {
globalToolState[imageId] = {};
}

const imageIdSpecificToolState = globalToolState[imageId];

if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL]) {
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL] = { data: [] };
} else if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data) {
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data = [];
}

const toolSpecificData =
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data;

toolSpecificData.push({ center: { x: xCenter, y: yCenter }, segmentNumber });

// Enable the tool if not enabled for the element.

const tool = cornerstoneTools.getToolForElement(
element,
DICOM_SEG_TEMP_CROSSHAIRS_TOOL
);

if (tool.mode !== 'enabled') {
// If not already active or passive, set passive so contours render.
cornerstoneTools.setToolEnabled(DICOM_SEG_TEMP_CROSSHAIRS_TOOL);
}
};
5 changes: 5 additions & 0 deletions extensions/dicom-segmentation/src/tools/TOOL_NAMES.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const TOOL_NAMES = {
DICOM_SEG_TEMP_CROSSHAIRS_TOOL: 'DICOMSegTempCrosshairsTool',
};

export default TOOL_NAMES;
Loading