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

91.42% Statements 32/35
77.77% Branches 14/18
100% Functions 7/7
90.9% Lines 30/33

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                1x   572x           572x 572x                                                       251x 251x   251x               251x       323x 323x   323x     251x 1x 1x     1x 1x 1x         1x     250x                 250x           250x   250x 322x   322x         322x               322x       250x 250x 250x       250x    
import cache from '../cache/cache';
import { EPSILON } from '../constants';
// import type { VolumeViewport } from '../RenderingEngine'
import { ICamera, IImageVolume, IVolumeViewport } from '../types';
import getSpacingInNormalDirection from './getSpacingInNormalDirection';
import { getVolumeLoaderSchemes } from '../loaders/volumeLoader';
 
// 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 targetVolumeId 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 targetVolumeId - If a target volumeId is given that volume
 * is forced to be used.
 *
 * @returns An object containing the imageVolume and spacingInNormalDirection.
 *
 */
export default function getTargetVolumeAndSpacingInNormalDir(
  viewport: IVolumeViewport,
  camera: ICamera,
  targetVolumeId?: string
): {
  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 (targetVolumeId) {
    const imageVolumeIndex = imageVolumes.findIndex(
      (iv) => iv.volumeId === targetVolumeId
    );
 
    const imageVolume = imageVolumes[imageVolumeIndex];
    const { uid: actorUID } = volumeActors[imageVolumeIndex];
    const spacingInNormalDirection = getSpacingInNormalDirection(
      imageVolume,
      viewPlaneNormal
    );
 
    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 = getSpacingInNormalDirection(
      imageVolume,
      viewPlaneNormal
    );
 
    // 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;
}