All files / packages/tools/src/utilities scroll.ts

82.6% Statements 19/23
62.5% Branches 10/16
100% Functions 2/2
82.6% Lines 19/23

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                                                  6x   6x       6x             6x   6x 1x   5x                           1x     1x   1x       1x 1x   1x                 1x       1x   1x     1x                   1x                         1x              
import {
  StackViewport,
  Types,
  VolumeViewport,
  eventTarget,
  EVENTS,
  utilities as csUtils,
  getEnabledElement,
} from '@cornerstonejs/core';
import { ScrollOptions, EventTypes } from '../types';
 
/**
 * It scrolls one slice in the Stack or Volume Viewport, it uses the options provided
 * to determine the slice to scroll to. For Stack Viewport, it scrolls in the 1 or -1
 * direction, for Volume Viewport, it uses the camera and focal point to determine the
 * slice to scroll to based on the spacings.
 * @param viewport - The viewport in which to scroll
 * @param options - Options to use for scrolling, including direction, invert, and volumeId
 * @returns
 */
export default function scroll(
  viewport: Types.IViewport,
  options: ScrollOptions
): void {
  // check if viewport is disabled then throw error
  const enabledElement = getEnabledElement(viewport.element);
 
  Iif (!enabledElement) {
    throw new Error('Scroll::Viewport is not enabled (it might be disabled)');
  }
 
  Iif (
    viewport instanceof StackViewport &&
    viewport.getImageIds().length === 0
  ) {
    throw new Error('Scroll::Stack Viewport has no images');
  }
 
  const { volumeId, delta, scrollSlabs } = options;
 
  if (viewport instanceof VolumeViewport) {
    scrollVolume(viewport, volumeId, delta, scrollSlabs);
  } else {
    (viewport as Types.IStackViewport).scroll(
      delta,
      options.debounceLoading,
      options.loop
    );
  }
}
 
export function scrollVolume(
  viewport: VolumeViewport,
  volumeId: string,
  delta: number,
  scrollSlabs = false
) {
  const useSlabThickness = scrollSlabs;
 
  const { numScrollSteps, currentStepIndex, sliceRangeInfo } =
    csUtils.getVolumeViewportScrollInfo(viewport, volumeId, useSlabThickness);
 
  Iif (!sliceRangeInfo) {
    return;
  }
 
  const { sliceRange, spacingInNormalDirection, camera } = sliceRangeInfo;
  const { focalPoint, viewPlaneNormal, position } = camera;
 
  const { newFocalPoint, newPosition } = csUtils.snapFocalPointToSlice(
    focalPoint,
    position,
    sliceRange,
    viewPlaneNormal,
    spacingInNormalDirection,
    delta
  );
 
  viewport.setCamera({
    focalPoint: newFocalPoint,
    position: newPosition,
  });
  viewport.render();
 
  const desiredStepIndex = currentStepIndex + delta;
 
  const VolumeScrollEventDetail: EventTypes.VolumeScrollOutOfBoundsEventDetail =
    {
      volumeId,
      viewport,
      delta,
      desiredStepIndex,
      currentStepIndex,
      numScrollSteps,
      currentImageId: viewport.getCurrentImageId(),
    };
 
  Iif (
    (desiredStepIndex > numScrollSteps || desiredStepIndex < 0) &&
    viewport.getCurrentImageId() // Check that we are in the plane of acquistion
  ) {
    // One common use case of this trigger might be to load the next
    // volume in a time series or the next segment of a partially loaded volume.
 
    csUtils.triggerEvent(
      eventTarget,
      EVENTS.VOLUME_SCROLL_OUT_OF_BOUNDS,
      VolumeScrollEventDetail
    );
  } else {
    csUtils.triggerEvent(
      eventTarget,
      EVENTS.VOLUME_VIEWPORT_SCROLL,
      VolumeScrollEventDetail
    );
  }
}