All files / packages/tools/src/utilities/planar filterAnnotationsWithinSlice.ts

92% Statements 23/25
62.5% Branches 5/8
100% Functions 2/2
91.66% Lines 22/24

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          1x   1x                               15x 5x                 5x   5x     5x     5x         5x             5x 5x   5x   5x 5x 5x   5x                   5x   5x   5x   5x 5x       5x    
import { vec3 } from 'gl-matrix';
import { CONSTANTS } from '@cornerstonejs/core';
import type { Types } from '@cornerstonejs/core';
import { Annotations, Annotation } from '../../types';
 
const { EPSILON } = CONSTANTS;
 
const PARALLEL_THRESHOLD = 1 - EPSILON;
 
/**
 * given some `Annotations`, and the slice defined by the camera's normal
 * direction and the spacing in the normal, filter the `Annotations` which
 * is within the slice.
 *
 * @param annotations - Annotations
 * @param camera - The camera
 * @param spacingInNormalDirection - The spacing in the normal direction
 * @returns The filtered `Annotations`.
 */
export default function filterAnnotationsWithinSlice(
  annotations: Annotations,
  camera: Types.ICamera,
  spacingInNormalDirection: number
): Annotations {
  const { viewPlaneNormal } = camera;
 
  // The reason we use parallel normals instead of actual orientation is that
  // flipped action is done through camera API, so we can't rely on the
  // orientation (viewplaneNormal and viewUp) since even the same image and
  // same slice if flipped will have different orientation, but still rendering
  // the same slice. Instead, we choose to use the parallel normals to filter
  // the annotations and later we fine tune it with the annotation within slice
  // logic down below.
  const annotationsWithParallelNormals = annotations.filter(
    (td: Annotation) => {
      const annotationViewPlaneNormal = td.metadata.viewPlaneNormal;
 
      const isParallel =
        Math.abs(vec3.dot(viewPlaneNormal, annotationViewPlaneNormal)) >
        PARALLEL_THRESHOLD;
 
      return annotationViewPlaneNormal && isParallel;
    }
  );
 
  // No in plane annotations.
  Iif (!annotationsWithParallelNormals.length) {
    return [];
  }
 
  // Annotation should be within the slice, which means that it should be between
  // camera's focalPoint +/- spacingInNormalDirection.
 
  const halfSpacingInNormalDirection = spacingInNormalDirection / 2;
  const { focalPoint } = camera;
 
  const annotationsWithinSlice = [];
 
  for (const annotation of annotationsWithParallelNormals) {
    const data = annotation.data;
    const point = data.handles.points[0];
 
    Iif (!annotation.isVisible) {
      continue;
    }
    // A = point
    // B = focal point
    // P = normal
 
    // B-A dot P  => Distance in the view direction.
    // this should be less than half the slice distance.
 
    const dir = vec3.create();
 
    vec3.sub(dir, focalPoint, point);
 
    const dot = vec3.dot(dir, viewPlaneNormal);
 
    Eif (Math.abs(dot) < halfSpacingInNormalDirection) {
      annotationsWithinSlice.push(annotation);
    }
  }
 
  return annotationsWithinSlice;
}