All files / packages/tools/src/tools/base AnnotationDisplayTool.ts

88.23% Statements 30/34
68.75% Branches 11/16
100% Functions 6/6
88.23% Lines 30/34

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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182                                                                                                                            119x       119x 119x   119x                       84x     1x   1x 1x 1x     1x   1x     1x   1x         1x 1x         1x       1x     1x 1x     1x                   68x       68x 61x 7x     7x 7x   7x             68x                                         1588x                 1x    
import {
  utilities,
  getEnabledElement,
  StackViewport,
  cache,
  VideoViewport,
} from '@cornerstonejs/core';
import type { Types } from '@cornerstonejs/core';
 
import BaseTool from './BaseTool';
import { getAnnotationManager } from '../../stateManagement/annotation/annotationState';
import { Annotation, Annotations, SVGDrawingHelper } from '../../types';
import triggerAnnotationRender from '../../utilities/triggerAnnotationRender';
import filterAnnotationsForDisplay from '../../utilities/planar/filterAnnotationsForDisplay';
import { getStyleProperty } from '../../stateManagement/annotation/config/helpers';
import { getState } from '../../stateManagement/annotation/config';
import { StyleSpecifier } from '../../types/AnnotationStyle';
 
/**
 * Abstract class for tools which create and display annotations on the
 * cornerstone3D canvas. In addition, it provides a base class for segmentation
 * tools that require drawing an annotation before running the segmentation strategy
 * for instance threshold segmentation based on an area and a threshold.
 * Annotation tools make use of drawing utilities to draw SVG elements on the viewport.
 *
 * To create a new annotation tool, derive from this class and implement the
 * abstract methods.
 */
abstract class AnnotationDisplayTool extends BaseTool {
  static toolName;
 
  // ===================================================================
  // Abstract Methods - Must be implemented.
  // ===================================================================
 
  /**
   * @abstract renderAnnotation it used to draw the tool's annotation in each
   * request animation frame
   *
   * @param enabledElement - The Cornerstone's enabledElement.
   * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
   */
  abstract renderAnnotation(
    enabledElement: Types.IEnabledElement,
    svgDrawingHelper: SVGDrawingHelper
  );
 
  /**
   * @virtual Given the element and annotations which is an array of annotation, it
   * filters the annotations array to only include the annotation based on the viewportType.
   * If the viewport is StackViewport, it filters based on the current imageId of the viewport,
   * if the viewport is volumeViewport, it only returns those that are within the
   * same slice as the current rendered slice in the volume viewport.
   * imageId as the enabledElement.
   * @param element - The HTML element
   * @param annotations - The annotations to filter (array of annotation)
   * @returns The filtered annotations
   */
  filterInteractableAnnotationsForElement(
    element: HTMLDivElement,
    annotations: Annotations
  ): Annotations | undefined {
    Iif (!annotations || !annotations.length) {
      return;
    }
 
    const enabledElement = getEnabledElement(element);
    const { viewport } = enabledElement;
 
    return filterAnnotationsForDisplay(viewport, annotations);
  }
 
  /**
   * On Image Calibration, take all the annotation from the AnnotationState manager,
   * and invalidate them to force them to be re-rendered and their stats to be recalculated.
   * Then use the old and new imageData (non-calibrated and calibrated) to calculate the
   * new position for the annotations in the space of the new imageData.
   *
   * @param evt - The calibration event
   *
   */
  public onImageSpacingCalibrated = (
    evt: Types.EventTypes.ImageSpacingCalibratedEvent
  ) => {
    const { element, imageId } = evt.detail;
 
    const imageURI = utilities.imageIdToURI(imageId);
    const annotationManager = getAnnotationManager();
    const framesOfReference = annotationManager.getFramesOfReference();
 
    // For each frame Of Reference
    framesOfReference.forEach((frameOfReference) => {
      const frameOfReferenceSpecificAnnotations =
        annotationManager.getAnnotations(frameOfReference);
 
      const toolSpecificAnnotations =
        frameOfReferenceSpecificAnnotations[this.getToolName()];
 
      Iif (!toolSpecificAnnotations || !toolSpecificAnnotations.length) {
        return;
      }
 
      // for this specific tool
      toolSpecificAnnotations.forEach((annotation) => {
        Iif (!annotation.metadata?.referencedImageId) {
          return;
        }
 
        // if the annotation is drawn on the same imageId
        const referencedImageURI = utilities.imageIdToURI(
          annotation.metadata.referencedImageId
        );
 
        Eif (referencedImageURI === imageURI) {
          // make them invalid since the image has been calibrated so that
          // we can update the cachedStats and also rendering
          annotation.invalidated = true;
          annotation.data.cachedStats = {};
        }
      });
      triggerAnnotationRender(element);
    });
  };
 
  protected getReferencedImageId(
    viewport: Types.IViewport,
    worldPos: Types.Point3,
    viewPlaneNormal: Types.Point3,
    viewUp: Types.Point3
  ): string {
    const targetId = this.getTargetId(viewport);
 
    let referencedImageId;
 
    if (viewport instanceof StackViewport) {
      referencedImageId = targetId.split('imageId:')[1];
    } else Iif (viewport instanceof VideoViewport) {
      referencedImageId = targetId.split('videoId:')[1];
    } else {
      const volumeId = utilities.getVolumeId(targetId);
      const imageVolume = cache.getVolume(volumeId);
 
      referencedImageId = utilities.getClosestImageId(
        imageVolume,
        worldPos,
        viewPlaneNormal
      );
    }
 
    return referencedImageId;
  }
 
  /**
   * It takes the property (color, lineDash, etc.) and based on the state of the
   * annotation (selected, highlighted etc.) it returns the appropriate value
   * based on the central toolStyle settings for each level of specification.
   * @param property - The name of the style property to get.
   * @param styleSpecifier - An object containing the specifications such as viewportId,
   * toolGroupId, toolName and annotationUID which are used to get the style if the level of specificity is
   * met (hierarchy is checked from most specific to least specific which is
   * annotationLevel -> viewportLevel -> toolGroupLevel -> default.
   * @param annotation - The annotation for the tool that is
   * currently active.
   * @returns The value of the property.
   */
  public getStyle(
    property: string,
    specifications: StyleSpecifier,
    annotation?: Annotation
  ): unknown {
    return getStyleProperty(
      property,
      specifications,
      getState(annotation),
      this.mode
    );
  }
}
 
AnnotationDisplayTool.toolName = 'AnnotationDisplayTool';
export default AnnotationDisplayTool;