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

0% Statements 0/39
0% Branches 0/16
0% Functions 0/4
0% Lines 0/39

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                                                                                                                                                                                                                                                           
import * as Types from '../types';
import cache, { ImageVolume } from '../cache';
import { ViewportType } from '../enums';
 
/**
 * Converts a volume viewport to a stack viewport.
 *
 * @param params - The parameters for the conversion.
 * @param params.viewport - The volume viewport to convert.
 * @param params.options - The conversion options.
 * @param [params.options.viewportId] - The new stackViewportId, If not provided, the volume viewport id will be used.
 * @param [params.options.background] - The background color of the stack viewport.
 * @param [params.options.decache] - Whether to decache the volume. Defaults to false.
 *
 * @returns The converted stack viewport.
 */
async function convertVolumeToStackViewport({
  viewport,
  options,
}: {
  viewport: Types.IVolumeViewport;
  options: {
    viewportId?: string;
    background?: Types.Point3;
  };
}): Promise<Types.IStackViewport> {
  const volumeViewport = viewport;
  const { id, element } = volumeViewport;
  const renderingEngine = viewport.getRenderingEngine();
  const imageIdIndex = viewport.getCurrentImageIdIndex();
 
  const { background } = options;
  const viewportId = options.viewportId || id;
 
  const actorEntry = volumeViewport.getDefaultActor();
  const { uid: volumeId } = actorEntry;
  const volume = cache.getVolume(volumeId) as Types.IImageVolume;
 
  if (!(volume instanceof ImageVolume)) {
    throw new Error(
      'Currently, you cannot decache a volume that is not an ImageVolume. So, unfortunately, volumes such as nifti  (which are basic Volume, without imageIds) cannot be decached.'
    );
  }
 
  const viewportInput = {
    viewportId,
    type: ViewportType.STACK,
    element,
    defaultOptions: {
      background,
    },
  };
 
  renderingEngine.enableElement(viewportInput);
 
  // Get the stack viewport that was created
  const stackViewport = <Types.IStackViewport>(
    renderingEngine.getViewport(viewportId)
  );
 
  // So here we have two scenarios that we need to handle:
  // 1. the volume was derived from a stack and we need to decache it, this is easy
  // since we just need purge the volume from the cache and those images will get
  // their copy of the image back
  // 2. It was actually a native volume and we need to decache it, this is a bit more
  // complicated since then we need to decide on the imageIds for it to get
  // decached to
  const hasCachedImages = volume.imageCacheOffsetMap.size > 0;
  // Initialize the variable to hold the final result
  let isAllImagesCached = false;
 
  if (hasCachedImages) {
    // Check if every imageId in the volume is in the _imageCache
    isAllImagesCached = volume.imageIds.every((imageId) =>
      cache.getImage(imageId)
    );
  }
 
  const volumeUsedInOtherViewports = renderingEngine
    .getVolumeViewports()
    .find((vp) => vp.hasVolumeId(volumeId));
 
  volume.decache(!volumeUsedInOtherViewports && isAllImagesCached);
 
  const stack = [...volume.imageIds].reverse();
 
  let imageIdIndexToJump = Math.max(
    volume.imageIds.length - imageIdIndex - 1,
    0
  );
 
  // Check to see if the image is already cached or not. If it's not, we will use another
  // nearby imageId for the first image to jump to. There seem to be a lot of side effects
  // if we jump to an image that is not cached in stack viewport while we convert
  // from a volume viewport. For example, if we switch back and forth between stack and volume,
  // and then try to jump to an image that is not cached, the image will not render at
  // all when the full volume is filled. I'm not sure why yet.
  const imageToJump = cache.getImage(stack[imageIdIndexToJump]);
  if (!imageToJump) {
    let minDistance = Infinity;
    let minDistanceIndex = null;
 
    stack.forEach((imageId, index) => {
      const image = cache.getImage(imageId);
      if (image) {
        const distance = Math.abs(imageIdIndexToJump - index);
        if (distance < minDistance) {
          minDistance = distance;
          minDistanceIndex = index;
        }
      }
    });
 
    imageIdIndexToJump = minDistanceIndex;
  }
 
  await stackViewport.setStack(stack, imageIdIndexToJump ?? 0);
 
  // Render the image
  stackViewport.render();
 
  return stackViewport;
}
 
export { convertVolumeToStackViewport };