All files / packages/core/src/utilities getTargetVolumeAndSpacingInNormalDir.ts

92.85% Statements 39/42
72.72% Branches 16/22
100% Functions 8/8
92.68% Lines 38/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                1x   1x 592x         1x 592x 592x                                                       273x 273x   273x               273x       321x 321x   321x     273x 1x 1x 1x     1x 1x   1x             1x     272x                 272x           272x   272x 320x   320x         320x                 320x       272x 272x 272x       272x                 321x 321x 321x 321x           321x    
import cache from '../cache/cache';
import { EPSILON } from '../constants';
import { 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 || !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.referenceId ?? 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];
 
    Iif (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.
    if (
      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 === false) {
    spacingInNormalDirection = getSpacingInNormalDirection(
      imageVolume,
      viewPlaneNormal
    );
  }
 
  return spacingInNormalDirection;
}