feat: Added support for TID 1410 relationship types (#2942)

* Added support for TID 1410 relationship types

* Added POLYLINE rendering from ladeirarodolfo to PR

* Rolled Dockerfile back to master to make merge easier

* Minor cleanup
This commit is contained in:
Sean Doyle 2022-10-10 23:09:38 -04:00 committed by GitHub
parent 7f770e4fb6
commit af09578544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 70 deletions

View File

@ -16,6 +16,7 @@ const sopClassUids = [
'1.2.840.10008.5.1.4.1.1.88.11', //BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11', //BASIC_TEXT_SR:
'1.2.840.10008.5.1.4.1.1.88.22', //ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22', //ENHANCED_SR:
'1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR:
'1.2.840.10008.5.1.4.1.1.88.34', //COMPREHENSIVE_3D_SR:
]; ];
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
@ -44,6 +45,7 @@ const CodingSchemeDesignators = {
const RELATIONSHIP_TYPE = { const RELATIONSHIP_TYPE = {
INFERRED_FROM: 'INFERRED FROM', INFERRED_FROM: 'INFERRED FROM',
CONTAINS: 'CONTAINS',
}; };
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
@ -376,7 +378,6 @@ function _processMeasurement(mergedContentSequence) {
function _processTID1410Measurement(mergedContentSequence) { function _processTID1410Measurement(mergedContentSequence) {
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level, // Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D // And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
// TODO -> Look at RelationshipType => Contains means
const graphicItem = mergedContentSequence.find( const graphicItem = mergedContentSequence.find(
group => group.ValueType === 'SCOORD' group => group.ValueType === 'SCOORD'
@ -536,9 +537,14 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
function _getCoordsFromSCOORDOrSCOORD3D(item) { function _getCoordsFromSCOORDOrSCOORD3D(item) {
const { ValueType, RelationshipType, GraphicType, GraphicData } = item; const { ValueType, RelationshipType, GraphicType, GraphicData } = item;
if (RelationshipType !== RELATIONSHIP_TYPE.INFERRED_FROM) { if (
!(
RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM ||
RelationshipType == RELATIONSHIP_TYPE.CONTAINS
)
) {
console.warn( console.warn(
`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM."` `Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`
); );
return; return;
@ -597,12 +603,18 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { _getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
const { ReferencedSOPSequence } = item; const { ReferencedSOPSequence } = item;
if (item.hasOwnProperty('ReferencedSOPClassUID')) {
const { const {
ReferencedSOPClassUID, ReferencedSOPClassUID,
ReferencedSOPInstanceUID, ReferencedSOPInstanceUID,
} = ReferencedSOPSequence; } = ReferencedSOPSequence;
referencedImages.push({ ReferencedSOPClassUID, ReferencedSOPInstanceUID }); referencedImages.push({
ReferencedSOPClassUID,
ReferencedSOPInstanceUID,
});
}
}); });
return referencedImages; return referencedImages;

View File

@ -99,7 +99,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
const annotation = filteredAnnotations[i]; const annotation = filteredAnnotations[i];
const annotationUID = annotation.annotationUID; const annotationUID = annotation.annotationUID;
const { renderableData } = annotation.data.cachedStats; const { renderableData } = annotation.data.cachedStats;
const { label, cachedStats } = annotation.data; const { cachedStats } = annotation.data;
const { referencedImageId } = annotation.metadata; const { referencedImageId } = annotation.metadata;
styleSpecifier.annotationUID = annotationUID; styleSpecifier.annotationUID = annotationUID;
@ -121,22 +121,30 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
const renderableDataForGraphicType = renderableData[GraphicType]; const renderableDataForGraphicType = renderableData[GraphicType];
let renderMethod; let renderMethod;
let renderTextBox;
let canvasCoordinatesAdapter;
switch (GraphicType) { switch (GraphicType) {
case SCOORD_TYPES.POINT: case SCOORD_TYPES.POINT:
renderMethod = this.renderPoint; renderMethod = this.renderPoint;
renderTextBox = this.renderTextBox;
break; break;
case SCOORD_TYPES.MULTIPOINT: case SCOORD_TYPES.MULTIPOINT:
renderMethod = this.renderMultipoint; renderMethod = this.renderMultipoint;
renderTextBox = this.renderTextBox;
break; break;
case SCOORD_TYPES.POLYLINE: case SCOORD_TYPES.POLYLINE:
renderMethod = this.renderPolyLine; renderMethod = this.renderPolyLine;
break; break;
case SCOORD_TYPES.CIRCLE: case SCOORD_TYPES.CIRCLE:
renderMethod = this.renderEllipse; renderMethod = this.renderEllipse;
renderTextBox = this.renderTextBox;
break; break;
case SCOORD_TYPES.ELLIPSE: case SCOORD_TYPES.ELLIPSE:
renderMethod = this.renderEllipse; renderMethod = this.renderEllipse;
renderTextBox = this.renderTextBox;
canvasCoordinatesAdapter =
utilities.math.ellipse.getCanvasEllipseCorners;
break; break;
default: default:
throw new Error(`Unsupported GraphicType: ${GraphicType}`); throw new Error(`Unsupported GraphicType: ${GraphicType}`);
@ -151,60 +159,18 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
options options
); );
if (!canvasCoordinates) { if (typeof renderTextBox === 'function') {
return; renderTextBox.call(
} this,
const textLines = this._getTextBoxLinesFromLabels(label);
let canvasCornersToUseForTextBox = canvasCoordinates;
if (GraphicType === SCOORD_TYPES.ELLIPSE) {
canvasCornersToUseForTextBox = utilities.math.ellipse.getCanvasEllipseCorners(
canvasCoordinates
);
}
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(
canvasCornersToUseForTextBox
);
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(
canvasTextBoxCoords
);
const textBoxPosition = viewport.worldToCanvas(
annotation.data.handles.textBox.worldPosition
);
const textBoxUID = '1';
const textBoxOptions = this.getLinkedTextBoxStyle(
styleSpecifier,
annotation
);
const boundingBox = drawing.drawLinkedTextBox(
svgDrawingHelper, svgDrawingHelper,
annotationUID, viewport,
textBoxUID,
textLines,
textBoxPosition,
canvasCoordinates, canvasCoordinates,
{}, canvasCoordinatesAdapter,
{ annotation,
...textBoxOptions, styleSpecifier,
color, options
}
); );
}
const { x: left, y: top, width, height } = boundingBox;
annotation.data.handles.textBox.worldBoundingBox = {
topLeft: viewport.canvasToWorld([left, top]),
topRight: viewport.canvasToWorld([left + width, top]),
bottomLeft: viewport.canvasToWorld([left, top + height]),
bottomRight: viewport.canvasToWorld([left + width, top + height]),
};
}); });
} }
}; };
@ -217,28 +183,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
referencedImageId, referencedImageId,
options options
) { ) {
// Todo: this needs to use the drawPolyLine from cs3D since it is implemented const drawingOptions = {
// now, before it was implemented with a loop over drawLine which is hacky color: options.color,
width: options.lineWidth,
};
let canvasCoordinates; let canvasCoordinates;
renderableData.map((data, index) => { renderableData.map((data, index) => {
canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
if (canvasCoordinates.length === 2) {
const lineUID = `${index}`; const lineUID = `${index}`;
if (canvasCoordinates.length === 2) {
drawing.drawLine( drawing.drawLine(
svgDrawingHelper, svgDrawingHelper,
annotationUID, annotationUID,
lineUID, lineUID,
canvasCoordinates[0], canvasCoordinates[0],
canvasCoordinates[1], canvasCoordinates[1],
{ drawingOptions
color: options.color,
width: options.lineWidth,
}
); );
} else { } else {
throw new Error('Drawing polyline for SR not yet implemented'); drawing.drawPolyline(
svgDrawingHelper,
annotationUID,
lineUID,
canvasCoordinates,
drawingOptions
);
} }
}); });
@ -367,6 +337,71 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
return canvasCoordinates; return canvasCoordinates;
} }
renderTextBox(
svgDrawingHelper,
viewport,
canvasCoordinates,
canvasCoordinatesAdapter,
annotation,
styleSpecifier,
options = {}
) {
if (!canvasCoordinates || !annotation) {
return;
}
const { annotationUID, data = {} } = annotation;
const { 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(label);
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(
adaptedCanvasCoordinates
);
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(
canvasTextBoxCoords
);
const textBoxPosition = viewport.worldToCanvas(
annotation.data.handles.textBox.worldPosition
);
const textBoxUID = '1';
const textBoxOptions = this.getLinkedTextBoxStyle(
styleSpecifier,
annotation
);
const boundingBox = drawing.drawLinkedTextBox(
svgDrawingHelper,
annotationUID,
textBoxUID,
textLines,
textBoxPosition,
canvasCoordinates,
{},
{
...textBoxOptions,
color,
}
);
const { x: left, y: top, width, height } = boundingBox;
annotation.data.handles.textBox.worldBoundingBox = {
topLeft: viewport.canvasToWorld([left, top]),
topRight: viewport.canvasToWorld([left + width, top]),
bottomLeft: viewport.canvasToWorld([left, top + height]),
bottomRight: viewport.canvasToWorld([left + width, top + height]),
};
}
} }
const SHORT_HAND_MAP = { const SHORT_HAND_MAP = {