All files / core/src/utilities getTargetVolumeAndSpacingInNormalDir.ts

95.23% Statements 40/42
75% Branches 15/20
100% Functions 8/8
95.12% Lines 39/41

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                428x   428x 10656x         428x 8024x 10656x                                                       3524x 3524x   3524x               3524x       5078x 5078x   5078x     3524x 170x 170x 170x     170x 170x   170x             170x     3354x                 3354x           3354x   3354x 4670x   4670x   1316x     3354x                 3354x       3354x 3354x 3354x       3354x                 3524x 3524x 3524x 3524x           3524x    
import cache from '../cache/cache';
import { EPSILON } from '../constants';
import type { ICamera, IImageVolume, IVolumeViewport, Point3 } from '../types';
import getSpacingInNormalDirection from './getSpacingInNormalDirection';
import { getVolumeLoaderSchemes } from '../loaders/volumeLoader';
import { getVolumeId } from './getVolumeId';
 
// One EPSILON part larger multiplier
const EPSILON_PART = 1 + EPSILON;
 
const startsWith = (str, starts) =>
  starts === str.substring(0, Math.min(str.length, starts.length));
 
// Check if this is a primary volume
// For now, that means it came from some sort of image loader, but
// should be specifically designated.
const isPrimaryVolume = (volume): boolean =>
  !!getVolumeLoaderSchemes().find((scheme) =>
    startsWith(volume.volumeId, scheme)
  );
 
/**
 * Given a volume viewport and camera, find the target volume.
 * The imageVolume is retrieved from cache for the specified targetId or
 * in case it is not provided, it chooses the volumeId on the viewport (there
 * might be more than one in case of fusion) that has the finest resolution in the
 * direction of view (normal).
 *
 * @param viewport - volume viewport
 * @param camera - current camera
 * @param targetId - If a targetId is forced to be used.
 * @param useSlabThickness - If true, the number of steps will be calculated
 * based on the slab thickness instead of the spacing in the normal direction
 * @returns An object containing the imageVolume and spacingInNormalDirection.
 *
 */
export default function getTargetVolumeAndSpacingInNormalDir(
  viewport: IVolumeViewport,
  camera: ICamera,
  targetId?: string,
  useSlabThickness = false
): {
  imageVolume: IImageVolume;
  spacingInNormalDirection: number;
  actorUID: string;
} {
  const { viewPlaneNormal } = camera;
  const volumeActors = viewport.getActors();
 
  Iif (!volumeActors.length) {
    return {
      spacingInNormalDirection: null,
      imageVolume: null,
      actorUID: null,
    };
  }
 
  const imageVolumes = volumeActors
    .map((va) => {
      // prefer the referenceUID if it is set, since it can be a derived actor
      // and the uid does not necessarily match the volumeId
      const actorUID = va.referencedId ?? va.uid;
      return cache.getVolume(actorUID);
    })
    .filter((iv) => !!iv);
 
  // If a volumeId is defined, set that volume as the target
  if (targetId) {
    const targetVolumeId = getVolumeId(targetId);
    const imageVolumeIndex = imageVolumes.findIndex((iv) =>
      targetVolumeId.includes(iv.volumeId)
    );
 
    const imageVolume = imageVolumes[imageVolumeIndex];
    const { uid: actorUID } = volumeActors[imageVolumeIndex];
 
    const spacingInNormalDirection = getSpacingInNormal(
      imageVolume,
      viewPlaneNormal,
      viewport,
      useSlabThickness
    );
 
    return { imageVolume, spacingInNormalDirection, actorUID };
  }
 
  Iif (!imageVolumes.length) {
    return {
      spacingInNormalDirection: null,
      imageVolume: null,
      actorUID: null,
    };
  }
 
  // Fetch volume actor with finest resolution in direction of projection.
  const smallest = {
    spacingInNormalDirection: Infinity,
    imageVolume: null,
    actorUID: null,
  };
 
  const hasPrimaryVolume = imageVolumes.find(isPrimaryVolume);
 
  for (let i = 0; i < imageVolumes.length; i++) {
    const imageVolume = imageVolumes[i];
 
    if (hasPrimaryVolume && !isPrimaryVolume(imageVolume)) {
      // Secondary volumes like segmentation don't count towards spacing
      continue;
    }
 
    const spacingInNormalDirection = getSpacingInNormal(
      imageVolume,
      viewPlaneNormal,
      viewport
    );
 
    // Allow for EPSILON part larger requirement to prefer earlier volumes
    // when the spacing is within a factor of EPSILON.  Use a factor because
    // that deals with very small or very large volumes effectively.
    Eif (
      spacingInNormalDirection * EPSILON_PART <
      smallest.spacingInNormalDirection
    ) {
      smallest.spacingInNormalDirection = spacingInNormalDirection;
      smallest.imageVolume = imageVolume;
      smallest.actorUID = volumeActors[i].uid;
    }
  }
 
  return smallest;
}
 
function getSpacingInNormal(
  imageVolume: IImageVolume,
  viewPlaneNormal: Point3,
  viewport: IVolumeViewport,
  useSlabThickness = false
): number {
  const { slabThickness } = viewport.getProperties();
  let spacingInNormalDirection = slabThickness;
  Eif (!slabThickness || !useSlabThickness) {
    spacingInNormalDirection = getSpacingInNormalDirection(
      imageVolume,
      viewPlaneNormal
    );
  }
 
  return spacingInNormalDirection;
}