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 716x         1x 716x 716x                                                       323x 323x   323x               323x       395x 395x   395x     323x 1x 1x 1x     1x 1x   1x             1x     322x                 322x           322x   322x 394x   394x         394x                 394x       322x 322x 322x       322x                 395x 395x 395x 395x           395x    
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;
}