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

21.73% Statements 10/46
20.83% Branches 5/24
30% Functions 3/10
22.22% Lines 10/45

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                            56x 56x   56x 56x       56x     1x       1x   1x                       1x 1x                                                                                                                                                                                                          
import cache, { ImageVolume } from '../cache';
import { Events } from '../enums';
import eventTarget from '../eventTarget';
import { getConfiguration, getShouldUseSharedArrayBuffer } from '../init';
 
/**
 * This function will check if the cache optimization is enabled and if it is
 * it will check if the created volume was derived from an already cached stack
 * of images, if so it will go back to the image cache and create a view at the
 * correct offset of the bigger volume array buffer, this will save memory.
 *
 * @param volumeId - The volumeId that will be checked for cache optimization
 */
export function setupCacheOptimizationEventListener(volumeId) {
  const { enableCacheOptimization } = getConfiguration();
  const shouldUseSAB = getShouldUseSharedArrayBuffer();
 
  const performOptimization = enableCacheOptimization && shouldUseSAB;
  Iif (!performOptimization) {
    return;
  }
 
  eventTarget.addEventListenerOnce(
    Events.IMAGE_VOLUME_LOADING_COMPLETED,
    (evt) => {
      Iif (evt.detail.volumeId !== volumeId) {
        return;
      }
 
      const volume = cache.getVolume(volumeId);
 
      performCacheOptimizationForVolume(volume);
    }
  );
}
 
/**
 * Performs cache optimization for a volume by replacing the pixel data of each image
 * in the image cache (if found) with a view of the volume's scalar data.
 * @param options - The options for cache optimization.
 * @param options.volumeId - The ID of the volume.
 */
export function performCacheOptimizationForVolume(volume) {
  Eif (!(volume instanceof ImageVolume)) {
    return;
  }
 
  const scalarData = volume.getScalarData();
 
  volume.imageCacheOffsetMap.size > 0
    ? _processImageCacheOffsetMap(volume, scalarData)
    : _processVolumeImages(volume, scalarData);
}
 
/**
 * This function will process the volume images and replace the pixel data of each
 * image in the image cache (if found) with a view of the volume's scalar data.
 * This function is used when the volume is derived from an already cached stack
 * of images.
 *
 * @param volume - The volume to process.
 * @param scalarData - The scalar data to use for the volume.
 */
function _processImageCacheOffsetMap(volume, scalarData) {
  volume.imageCacheOffsetMap.forEach(({ offset }, imageId) => {
    const image = cache.getImage(imageId);
    if (!image) {
      return;
    }
 
    _updateImageWithScalarDataView(image, scalarData, offset);
    cache.decrementImageCacheSize(image.sizeInBytes);
  });
}
 
/**
 * This function will process the volume images and replace the pixel data of each
 * image in the image cache (if found) with a view of the volume's scalar data.
 * This function is used when the volume is not derived from an already cached stack
 * of images.
 *
 * @param volume - The volume to process.
 * @param scalarData - The scalar data to use for the volume.
 */
function _processVolumeImages(volume, scalarData) {
  let compatibleScalarData = scalarData;
 
  const sampleImageIdWithImage = volume.imageIds.find((imageId) => {
    const image = cache.getImage(imageId);
    return image;
  });
 
  if (!sampleImageIdWithImage) {
    return;
  }
 
  const sampleImage = cache.getImage(sampleImageIdWithImage);
  const samplePixelData =
    sampleImage.imageFrame?.pixelData || sampleImage.getPixelData();
 
  // Check if the types of scalarData and pixelData are different.
  if (scalarData.constructor !== samplePixelData.constructor) {
    // If so, create a new typed array of the same type as pixelData and copy the values from scalarData.
    compatibleScalarData = new samplePixelData.constructor(scalarData.length);
 
    // Copy values from scalarData to compatibleScalarData.
    compatibleScalarData.set(scalarData);
  }
 
  volume.imageIds.forEach((imageId) => {
    const image = cache.getImage(imageId);
    if (!image) {
      return;
    }
 
    const index = volume.getImageIdIndex(imageId);
    const offset = index * image.getPixelData().byteLength;
 
    _updateImageWithScalarDataView(image, compatibleScalarData, offset);
    cache.decrementImageCacheSize(image.sizeInBytes);
  });
}
 
function _updateImageWithScalarDataView(image, scalarData, offset) {
  const pixelData = image.imageFrame
    ? image.imageFrame.pixelData
    : image.getPixelData();
 
  const view = new pixelData.constructor(
    scalarData.buffer,
    offset,
    pixelData.length
  );
 
  image.getPixelData = () => view;
 
  if (image.imageFrame) {
    image.imageFrame.pixelData = view;
  }
 
  image.bufferView = {
    buffer: scalarData.buffer,
    offset,
  };
}