All files / core/src/utilities createSubVolume.ts

0% Statements 0/27
0% Branches 0/5
0% Functions 0/1
0% Lines 0/25

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                                                                                                                                                                                                                                             
import transformWorldToIndex from './transformWorldToIndex';
import transformIndexToWorld from './transformIndexToWorld';
import uuidv4 from './uuidv4';
import { createLocalVolume } from '../loaders/volumeLoader';
import cache from '../cache/cache';
import type {
  AABB3,
  PixelDataTypedArray,
  PixelDataTypedArrayString,
  Point3,
} from '../types';
 
/**
 * @param boundsIJK - Array that contains [[minX, maxX], [minY, maxY], [minZ, maxZ]]
 */
function createSubVolume(
  referencedVolumeId: string,
  boundsIJK: AABB3,
  options: {
    targetBuffer?: {
      type: PixelDataTypedArrayString;
    };
  } = {}
) {
  const referencedVolume = cache.getVolume(referencedVolumeId);
 
  if (!referencedVolume) {
    throw new Error(
      `Referenced volume with id ${referencedVolumeId} does not exist.`
    );
  }
 
  const {
    metadata,
    spacing,
    direction,
    dimensions: refVolumeDim,
  } = referencedVolume;
 
  const { minX, maxX, minY, maxY, minZ, maxZ } = boundsIJK;
 
  const ijkTopLeft: Point3 = [
    Math.min(minX, maxX),
    Math.min(minY, maxY),
    Math.min(minZ, maxZ),
  ];
 
  const boundingBoxOriginWorld = transformIndexToWorld(
    referencedVolume.imageData,
    ijkTopLeft
  );
  const dimensions: Point3 = [
    Math.abs(maxX - minX) + 1,
    Math.abs(maxY - minY) + 1,
    Math.abs(maxZ - minZ) + 1,
  ];
 
  const { targetBuffer } = options;
  const subVolumeOptions = {
    metadata,
    dimensions,
    spacing,
    origin: boundingBoxOriginWorld,
    direction,
    targetBuffer,
    scalarData:
      targetBuffer?.type === 'Float32Array'
        ? new Float32Array(dimensions[0] * dimensions[1] * dimensions[2])
        : undefined,
  };
 
  const subVolume = createLocalVolume(uuidv4(), subVolumeOptions);
 
  const subVolumeData = subVolume.voxelManager.getCompleteScalarDataArray();
  const subVolumeSliceSize = dimensions[0] * dimensions[1];
  const refVolumeSliceSize = refVolumeDim[0] * refVolumeDim[1];
  const refVolumeData =
    referencedVolume.voxelManager.getCompleteScalarDataArray() as PixelDataTypedArray;
 
  for (let z = 0; z < dimensions[2]; z++) {
    for (let y = 0; y < dimensions[1]; y++) {
      // Get the position of the first voxel of a row and copy the entire row.
      // That is possible because the volumes have the same orientation.
      const rowStartWorld = transformIndexToWorld(subVolume.imageData, [
        0,
        y,
        z,
      ]);
 
      const refVolumeRowStartIJK = transformWorldToIndex(
        referencedVolume.imageData,
        rowStartWorld
      );
      const refVolumeRowStartOffset =
        refVolumeRowStartIJK[2] * refVolumeSliceSize +
        refVolumeRowStartIJK[1] * refVolumeDim[0] +
        refVolumeRowStartIJK[0];
 
      const rowData = refVolumeData.slice(
        refVolumeRowStartOffset,
        refVolumeRowStartOffset + dimensions[0]
      );
      const subVolumeLineStartOffset =
        z * subVolumeSliceSize + y * dimensions[0];
 
      // @ts-expect-error
      subVolumeData.set(rowData, subVolumeLineStartOffset);
    }
  }
 
  subVolume.voxelManager.setCompleteScalarDataArray(subVolumeData);
 
  // cache.putVolumeSync(subVolume.volumeId, subVolume);
 
  return subVolume;
}
 
export { createSubVolume as default, createSubVolume };