Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 16 additions & 2 deletions extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ function _processTID1410Measurement(mergedContentSequence) {

const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');

const finding = mergedContentSequence.find(
item => item.ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.Finding
);

const { ConceptNameCodeSequence: conceptNameItem } = graphicItem;
const { CodeValue: graphicValue, CodingSchemeDesignator: graphicDesignator } = conceptNameItem;
const graphicCode = `${graphicDesignator}:${graphicValue}`;
Expand Down Expand Up @@ -547,12 +551,18 @@ function _processTID1410Measurement(mergedContentSequence) {
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSiteSCT
);
if (findingSites.length) {
const siteItem = findingSites[0];
const conceptName = siteItem.ConceptNameCodeSequence;
measurement.labels.push({
label: CodeNameCodeSequenceValues.FindingSiteSCT,
value: findingSites[0].ConceptCodeSequence.CodeMeaning,
label: conceptName?.CodeMeaning || 'Finding Site',
value: siteItem.ConceptCodeSequence.CodeMeaning,
});
}

const measurementWithFinding = measurement as Record<string, unknown>;
measurementWithFinding.srFinding = finding?.ConceptCodeSequence;
measurementWithFinding.srFindingSites = findingSites.map(fs => fs.ConceptCodeSequence).filter(Boolean);

return measurement;
}

Expand Down Expand Up @@ -660,6 +670,10 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
}
});

const measurementWithFinding = measurement as Record<string, unknown>;
measurementWithFinding.srFinding = finding?.ConceptCodeSequence;
measurementWithFinding.srFindingSites = findingSites.map(fs => fs.ConceptCodeSequence).filter(Boolean);

return measurement;
}

Expand Down
14 changes: 11 additions & 3 deletions extensions/cornerstone-dicom-sr/src/init.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
addTool,
AngleTool,
annotation,
ArrowAnnotateTool,
Expand All @@ -15,6 +16,8 @@ import { Types } from '@ohif/core';
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
import addToolInstance from './utils/addToolInstance';
import toolNames from './tools/toolNames';
import SRPointTool from './tools/SRPointTool';
import { getSRRectangleROITextLines } from './utils/srToolGetTextLines';

/**
* @param {object} configuration
Expand All @@ -31,17 +34,22 @@ export default function init({
addToolInstance(toolNames.SRArrowAnnotate, ArrowAnnotateTool);
addToolInstance(toolNames.SRAngle, AngleTool);
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);

// TODO - fix the SR display of Cobb Angle, as it joins the two lines
/** SR subtypes: show label (e.g. Lesion) instead of intensity/stats */
addTool(SRPointTool);
addToolInstance(toolNames.SRRectangleROI, RectangleROITool, {
getTextLines: getSRRectangleROITextLines,
});

/** TODO - fix the SR display of Cobb Angle, as it joins the two lines */
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);

// Modify annotation tools to use dashed lines on SR
const dashedLine = {
lineDash: '4,4',
};
annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
[toolNames.DICOMSRDisplay]: dashedLine,
[toolNames.SRPoint]: dashedLine,
SRLength: dashedLine,
SRBidirectional: dashedLine,
SREllipticalROI: dashedLine,
Expand Down
96 changes: 52 additions & 44 deletions extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,29 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
_getTextBoxLinesFromLabels(labels) {
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!

if (!labels?.length) {
return [];
}

const labelLength = Math.min(labels.length, 5);
const lines = [];

for (let i = 0; i < labelLength; i++) {
const labelEntry = labels[i];
lines.push(`${_labelToShorthand(labelEntry.label)}: ${labelEntry.value}`);
const shorthand = _labelToShorthand(labelEntry.label);
const value = labelEntry.value ?? '';

/**
* Empty shorthand (CORNERSTONEFREETEXT, Length, etc.): show value only — avoids ": text" or
* "363698007: site" when label was a raw code (fixed at source for finding site).
*/
if (shorthand === '' && value !== '') {
lines.push(String(value));
} else if (shorthand === '') {
continue;
} else {
lines.push(`${shorthand}: ${value}`);
}
}

return lines;
Expand Down Expand Up @@ -225,55 +242,41 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
options
) {
const canvasCoordinates = [];
const crossSize = 6;

renderableData.map((data, index) => {
const point = data[0];
// This gives us one point for arrow
const [canvasX, canvasY] = viewport.worldToCanvas(point);
canvasCoordinates.push(viewport.worldToCanvas(point));

if (data[1] !== undefined) {
canvasCoordinates.push(viewport.worldToCanvas(data[1]));
drawing.drawArrow(
svgDrawingHelper,
annotationUID,
`arrow-${index}`,
canvasCoordinates[canvasCoordinates.length - 1],
canvasCoordinates[canvasCoordinates.length - 2],
{ color: options.color, width: options.lineWidth }
);
} else {
const cx = Number(canvasX);
const cy = Number(canvasY);
const crossPaths = [
[[cx, cy - crossSize], [cx, cy + crossSize]],
[[cx - crossSize, cy], [cx + crossSize, cy]],
];
drawing.drawPath(
svgDrawingHelper,
annotationUID,
`cross-${index}`,
crossPaths,
{ color: options.color, lineWidth: options.lineWidth || 2 }
);
}
else{
// We get the other point for the arrow by using the image size
const imagePixelModule = metaData.get('imagePixelModule', referencedImageId);

let xOffset = 10;
let yOffset = 10;

if (imagePixelModule) {
const { columns, rows } = imagePixelModule;
xOffset = columns / 10;
yOffset = rows / 10;
}

const imagePoint = csUtils.worldToImageCoords(referencedImageId, point);
const arrowEnd = csUtils.imageToWorldCoords(referencedImageId, [
imagePoint[0] + xOffset,
imagePoint[1] + yOffset,
]);

canvasCoordinates.push(viewport.worldToCanvas(arrowEnd));

}


const arrowUID = `${index}`;

// Todo: handle drawing probe as probe, currently we are drawing it as an arrow
drawing.drawArrow(
svgDrawingHelper,
annotationUID,
arrowUID,
canvasCoordinates[1],
canvasCoordinates[0],
{
color: options.color,
width: options.lineWidth,
}
);
});

return canvasCoordinates; // used for drawing textBox
return canvasCoordinates;
}

renderEllipse(
Expand Down Expand Up @@ -343,15 +346,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
}

const { annotationUID, data = {} } = annotation;
const { labels } = data;
const { labels, label } = data;
const { color } = options;

let adaptedCanvasCoordinates = canvasCoordinates;
// adapt coordinates if there is an adapter

if (typeof canvasCoordinatesAdapter === 'function') {
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
}
const textLines = this._getTextBoxLinesFromLabels(labels);

const textLines =
typeof label === 'string' && label.length > 0
? [label]
: this._getTextBoxLinesFromLabels(labels);
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);

if (!annotation.data?.handles?.textBox?.worldPosition) {
Expand All @@ -374,6 +381,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
{
...textBoxOptions,
color,
padding: textBoxOptions.padding ?? 6,
}
);

Expand Down
118 changes: 118 additions & 0 deletions extensions/cornerstone-dicom-sr/src/tools/SRPointTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { Types } from '@cornerstonejs/core';
import { ProbeTool, drawing, annotation } from '@cornerstonejs/tools';

import type { SVGDrawingHelper } from '@cornerstonejs/tools';
import type { StyleSpecifier } from '@cornerstonejs/tools';
import type { ProbeAnnotation } from '@cornerstonejs/tools';

import { getSRPointTextLines } from '../utils/srToolGetTextLines';

const { drawPath, drawTextBox } = drawing;
const { getAnnotations } = annotation.state;
const { isAnnotationVisible } = annotation.visibility;

const CROSS_SIZE = 6;

/**
* SRPoint: sub-type of Probe for hydrated DICOM SR SCOORD/SCOORD3D points.
* Renders a cross (plus) marker per medical imaging convention;
* shows label only, no intensity/coordinates.
*/
class SRPointTool extends ProbeTool {
static toolName = 'SRPoint';

constructor(toolProps, defaultToolProps) {
super(toolProps, {
...defaultToolProps,
configuration: {
...defaultToolProps?.configuration,
getTextLines: getSRPointTextLines,
handleRadius: 6,
textCanvasOffset: { x: 4, y: 4 },
},
});
}

renderAnnotation = (
enabledElement: Types.IEnabledElement,
svgDrawingHelper: SVGDrawingHelper
): boolean => {
let renderStatus = false;
const { viewport } = enabledElement;
const { element } = viewport;

const annotations = getAnnotations(this.getToolName(), element) as ProbeAnnotation[];
if (!annotations?.length) {
return renderStatus;
}

const filteredAnnotations = this.filterInteractableAnnotationsForElement(
element,
annotations
);
if (!filteredAnnotations?.length) {
return renderStatus;
}

const styleSpecifier: StyleSpecifier = {
toolGroupId: this.toolGroupId,
toolName: this.getToolName(),
viewportId: enabledElement.viewport.id,
};

for (const annotation of filteredAnnotations) {
const annotationUID = annotation.annotationUID;
const data = annotation.data;
const point = data.handles.points[0];
const [canvasX, canvasY] = viewport.worldToCanvas(point);

if (!isAnnotationVisible(annotationUID)) {
continue;
}

styleSpecifier.annotationUID = annotationUID;
const { color, lineWidth } = this.getAnnotationStyle({
annotation,
styleSpecifier,
});

const size = Number(this.configuration?.handleRadius) || CROSS_SIZE;
const cx = Number(canvasX);
const cy = Number(canvasY);
const crossPaths: [number, number][][] = [
[[cx, cy - size], [cx, cy + size]],
[[cx - size, cy], [cx + size, cy]],
];
drawPath(svgDrawingHelper, annotationUID, 'cross', crossPaths, {
color,
lineWidth: lineWidth || 2,
});

renderStatus = true;

const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
if (options?.visibility !== false) {
const targetId = this.getTargetId(viewport, data);
const textLines = getSRPointTextLines(data, targetId);
if (textLines?.length) {
const textCanvasCoordinates = [
cx + (this.configuration?.textCanvasOffset?.x ?? 4),
cy + (this.configuration?.textCanvasOffset?.y ?? 4),
];
drawTextBox(
svgDrawingHelper,
annotationUID,
'0',
textLines,
[textCanvasCoordinates[0], textCanvasCoordinates[1]],
{ ...options, padding: options.padding ?? 4 }
);
}
}
}

return renderStatus;
};
}

export default SRPointTool;
1 change: 1 addition & 0 deletions extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const toolNames = {
SRCobbAngle: 'SRCobbAngle',
SRRectangleROI: 'SRRectangleROI',
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
SRPoint: 'SRPoint',
};

export default toolNames;
Loading
Loading