All files / extensions/cornerstone-dicom-sr/src/utils addSRAnnotation.ts

78.26% Statements 18/23
61.53% Branches 8/13
100% Functions 2/2
78.26% Lines 18/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143              53x                                                                                     2x           2x 2x 2x 2x     2x 2x 2x     2x 2x   2x 2x 2x     2x                                   2x                   2x                                                                         2x    
import { Types, annotation } from '@cornerstonejs/tools';
import { metaData } from '@cornerstonejs/core';
import { adaptersSR } from '@cornerstonejs/adapters';
 
import getRenderableData from './getRenderableData';
import toolNames from '../tools/toolNames';
 
const { MeasurementReport } = adaptersSR.Cornerstone3D;
 
/**
 * Adds a DICOM SR (Structured Report) annotation to the annotation manager.
 * This function processes measurement data from DICOM SR and converts it into
 * a format suitable for display in the Cornerstone3D viewer.
 *
 * @param {Object} params - The parameters object
 * @param {Object} params.measurement - The DICOM SR measurement data containing coordinates, labels, and metadata
 * @param {Array} params.measurement.coords - Array of coordinate objects with GraphicType, ValueType, and other properties
 * @param {string} params.measurement.TrackingUniqueIdentifier - Unique identifier for the measurement
 * @param {string} params.measurement.TrackingIdentifier - Tracking identifier for adapter lookup
 * @param {Array} [params.measurement.labels] - Optional array of label objects
 * @param {string} [params.measurement.displayText] - Optional display text for the annotation
 * @param {Object} [params.measurement.textBox] - Optional text box configuration
 * @param {string|null} [params.imageId] - Optional image ID for the referenced image (defaults to null)
 * @param {number|null} [params.frameNumber] - Optional frame number for multi-frame images (defaults to null)
 * @param {Object} params.displaySet - The display set containing the image
 * @param {string} params.displaySet.displaySetInstanceUID - Unique identifier for the display set
 * @returns {void}
 *
 * @example
 * ```typescript
 * addSRAnnotation({
 *   measurement: {
 *     TrackingUniqueIdentifier: '1.2.3.4.5',
 *     TrackingIdentifier: 'POINT',
 *     coords: [{
 *       GraphicType: 'POINT',
 *       ValueType: 'SCOORD',
 *       // ... other coordinate properties
 *     }],
 *     labels: [{ value: 'Measurement Point' }],
 *     displayText: 'Point measurement'
 *   },
 *   imageId: 'wadouri:file://path/to/image.dcm', // Optional
 *   frameNumber: 0, // Optional
 *   displaySet: { displaySetInstanceUID: '1.2.3.4' }
 * });
 * ```
 */
export default function addSRAnnotation({ measurement, imageId = null, frameNumber = null, displaySet }) {
  /** @type {string} The tool name to use for the annotation, defaults to DICOMSRDisplay */
  let toolName = toolNames.DICOMSRDisplay;
  
  /** 
   * @type {Object} Renderable data organized by graphic type
   * Groups coordinate data by GraphicType for efficient rendering
   */
  const renderableData = measurement.coords.reduce((acc, coordProps) => {
    acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
    acc[coordProps.GraphicType].push(getRenderableData({ ...coordProps, imageId }));
    return acc;
  }, {});
 
  const { TrackingUniqueIdentifier } = measurement;
  const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0];
  const graphicTypePoints = renderableData[graphicType];
 
  /** TODO: Read the tool name from the DICOM SR identification type in the future. */
  let frameOfReferenceUID = null;
  let planeRestriction = null;
 
  if (imageId) {
    const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
    frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
  }
 
  Iif (valueType === 'SCOORD3D') {
    const adapter = MeasurementReport.getAdapterForTrackingIdentifier(
      measurement.TrackingIdentifier
    );
    Iif (!adapter) {
      toolName = toolNames.SRSCOORD3DPoint;
    }
 
    // get the ReferencedFrameOfReferenceUID from the measurement
    frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
 
    planeRestriction = {
      FrameOfReferenceUID: frameOfReferenceUID,
      point: graphicTypePoints[0][0],
    };
  }
 
  // Store the view reference for use in initial navigation
  measurement.viewReference = {
    planeRestriction,
    FrameOfReferenceUID: frameOfReferenceUID,
    referencedImageId: imageId,
  };
 
  /**
   * @type {Types.Annotation} The annotation object to be added to the annotation manager
   * Contains all necessary metadata and data for rendering the DICOM SR measurement
   */
  const SRAnnotation: Types.Annotation = {
    annotationUID: TrackingUniqueIdentifier,
    highlighted: false,
    isLocked: false,
    isPreview: toolName === toolNames.DICOMSRDisplay,
    invalidated: false,
    metadata: {
      toolName,
      planeRestriction,
      valueType,
      graphicType,
      FrameOfReferenceUID: frameOfReferenceUID,
      referencedImageId: imageId,
      displaySetInstanceUID: displaySet.displaySetInstanceUID,
    },
    data: {
      label: measurement.labels?.[0]?.value || undefined,
      displayText: measurement.displayText || undefined,
      handles: {
        textBox: measurement.textBox ?? {},
        points: graphicTypePoints[0],
      },
      cachedStats: {},
      frameNumber,
      renderableData,
      TrackingUniqueIdentifier,
      labels: measurement.labels,
    },
  };
 
  /**
   * Add the annotation to the annotation state manager.
   * Note: Using annotation.state.addAnnotation() instead of annotationManager.addAnnotation()
   * because the latter was not triggering annotation_added events properly.
   * 
   * @param {Types.Annotation} SRAnnotation - The annotation to add
   */
  annotation.state.addAnnotation(SRAnnotation);
}