From af095785441bc6f098c589a40c8583407260fb4e Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Mon, 10 Oct 2022 23:09:38 -0400 Subject: [PATCH] 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 --- .../src/getSopClassHandlerModule.js | 28 ++- .../src/tools/DICOMSRDisplayTool.ts | 159 +++++++++++------- 2 files changed, 117 insertions(+), 70 deletions(-) diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js index d41d6d147..a9bb0abd9 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js @@ -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.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.34', //COMPREHENSIVE_3D_SR: ]; const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; @@ -44,6 +45,7 @@ const CodingSchemeDesignators = { const RELATIONSHIP_TYPE = { INFERRED_FROM: 'INFERRED FROM', + CONTAINS: 'CONTAINS', }; const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; @@ -376,7 +378,6 @@ function _processMeasurement(mergedContentSequence) { function _processTID1410Measurement(mergedContentSequence) { // 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 - // TODO -> Look at RelationshipType => Contains means const graphicItem = mergedContentSequence.find( group => group.ValueType === 'SCOORD' @@ -536,9 +537,14 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { function _getCoordsFromSCOORDOrSCOORD3D(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( - `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; @@ -597,12 +603,18 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { _getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { const { ReferencedSOPSequence } = item; - const { - ReferencedSOPClassUID, - ReferencedSOPInstanceUID, - } = ReferencedSOPSequence; - referencedImages.push({ ReferencedSOPClassUID, ReferencedSOPInstanceUID }); + if (item.hasOwnProperty('ReferencedSOPClassUID')) { + const { + ReferencedSOPClassUID, + ReferencedSOPInstanceUID, + } = ReferencedSOPSequence; + + referencedImages.push({ + ReferencedSOPClassUID, + ReferencedSOPInstanceUID, + }); + } }); return referencedImages; diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts index 809abea6e..536c8681a 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -99,7 +99,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const annotation = filteredAnnotations[i]; const annotationUID = annotation.annotationUID; const { renderableData } = annotation.data.cachedStats; - const { label, cachedStats } = annotation.data; + const { cachedStats } = annotation.data; const { referencedImageId } = annotation.metadata; styleSpecifier.annotationUID = annotationUID; @@ -121,22 +121,30 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const renderableDataForGraphicType = renderableData[GraphicType]; let renderMethod; + let renderTextBox; + let canvasCoordinatesAdapter; switch (GraphicType) { case SCOORD_TYPES.POINT: renderMethod = this.renderPoint; + renderTextBox = this.renderTextBox; break; case SCOORD_TYPES.MULTIPOINT: renderMethod = this.renderMultipoint; + renderTextBox = this.renderTextBox; break; case SCOORD_TYPES.POLYLINE: renderMethod = this.renderPolyLine; break; case SCOORD_TYPES.CIRCLE: renderMethod = this.renderEllipse; + renderTextBox = this.renderTextBox; break; case SCOORD_TYPES.ELLIPSE: renderMethod = this.renderEllipse; + renderTextBox = this.renderTextBox; + canvasCoordinatesAdapter = + utilities.math.ellipse.getCanvasEllipseCorners; break; default: throw new Error(`Unsupported GraphicType: ${GraphicType}`); @@ -151,60 +159,18 @@ export default class DICOMSRDisplayTool extends AnnotationTool { options ); - if (!canvasCoordinates) { - return; - } - - const textLines = this._getTextBoxLinesFromLabels(label); - - let canvasCornersToUseForTextBox = canvasCoordinates; - - if (GraphicType === SCOORD_TYPES.ELLIPSE) { - canvasCornersToUseForTextBox = utilities.math.ellipse.getCanvasEllipseCorners( - canvasCoordinates + if (typeof renderTextBox === 'function') { + renderTextBox.call( + this, + svgDrawingHelper, + viewport, + canvasCoordinates, + canvasCoordinatesAdapter, + annotation, + styleSpecifier, + options ); } - - 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, - 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]), - }; }); } }; @@ -217,28 +183,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool { referencedImageId, options ) { - // Todo: this needs to use the drawPolyLine from cs3D since it is implemented - // now, before it was implemented with a loop over drawLine which is hacky - + const drawingOptions = { + color: options.color, + width: options.lineWidth, + }; let canvasCoordinates; renderableData.map((data, index) => { canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); + const lineUID = `${index}`; if (canvasCoordinates.length === 2) { - const lineUID = `${index}`; drawing.drawLine( svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], - { - color: options.color, - width: options.lineWidth, - } + drawingOptions ); } 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; } + + 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 = {