All files / tools/src/utilities/segmentation utilities.ts

53.48% Statements 23/43
33.33% Branches 4/12
42.85% Functions 3/7
53.65% Lines 22/41

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 147 148 149 150 151 152 153 154 155 156 157                                          428x 8x                                                                                                                     8x   8x     8x 8x 8x   8x     8x   8x       8x       8x 8x   8x                     8x           428x         428x 2245x 2245x         428x             428x               428x            
import type { Types } from '@cornerstonejs/core';
import { utilities as csUtils } from '@cornerstonejs/core';
import { getBoundingBoxAroundShapeIJK } from '../boundingBox/getBoundingBoxAroundShape';
import type vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
 
export type ThresholdInformation = {
  volume: Types.IImageVolume;
  lower: number;
  upper: number;
};
 
export type VolumeInfo = {
  imageData: vtkImageData;
  lower: number;
  upper: number;
  spacing: Types.Point3;
  dimensions: Types.Point3;
  volumeSize: number;
  voxelManager: Types.IVoxelManager<number> | Types.IVoxelManager<Types.RGB>;
};
 
const equalsCheck = (a, b) => {
  return JSON.stringify(a) === JSON.stringify(b);
};
 
/**
 * Given the center of a voxel in world coordinates, calculate the voxel
 * corners in world coords to calculate the voxel overlap in another volume
 */
export function getVoxelOverlap(
  imageData,
  dimensions,
  voxelSpacing,
  voxelCenter
) {
  // Pre-calculate half spacings
  const halfSpacingX = voxelSpacing[0] / 2;
  const halfSpacingY = voxelSpacing[1] / 2;
  const halfSpacingZ = voxelSpacing[2] / 2;
 
  // Pre-allocate array for 8 corners
  const voxelCornersIJK = new Array(8);
 
  // Calculate first corner
  voxelCornersIJK[0] = csUtils.transformWorldToIndex(imageData, [
    voxelCenter[0] - halfSpacingX,
    voxelCenter[1] - halfSpacingY,
    voxelCenter[2] - halfSpacingZ,
  ]) as Types.Point3;
 
  // Define offsets for remaining 7 corners
  const offsets = [
    [1, -1, -1],
    [-1, 1, -1],
    [1, 1, -1],
    [-1, -1, 1],
    [1, -1, 1],
    [-1, 1, 1],
    [1, 1, 1],
  ];
 
  // Calculate remaining corners
  for (let i = 0; i < 7; i++) {
    const [xOff, yOff, zOff] = offsets[i];
    voxelCornersIJK[i + 1] = csUtils.transformWorldToIndex(imageData, [
      voxelCenter[0] + xOff * halfSpacingX,
      voxelCenter[1] + yOff * halfSpacingY,
      voxelCenter[2] + zOff * halfSpacingZ,
    ]) as Types.Point3;
  }
 
  return getBoundingBoxAroundShapeIJK(voxelCornersIJK, dimensions);
}
 
/**
 * Prepare a list of volume information objects for callback functions
 */
export function processVolumes(
  segmentationVolume: Types.IImageVolume,
  thresholdVolumeInformation: ThresholdInformation[]
) {
  const { spacing: segmentationSpacing } = segmentationVolume;
  const scalarDataLength =
    segmentationVolume.voxelManager.getScalarDataLength();
 
  // prepare a list of volume information objects for callback functions
  const volumeInfoList: VolumeInfo[] = [];
  let baseVolumeIdx = 0;
  for (let i = 0; i < thresholdVolumeInformation.length; i++) {
    const { imageData, spacing, dimensions, voxelManager } =
      thresholdVolumeInformation[i].volume;
 
    const volumeSize =
      thresholdVolumeInformation[i].volume.voxelManager.getScalarDataLength();
    // discover the index of the volume the segmentation data is based on
    Eif (
      volumeSize === scalarDataLength &&
      equalsCheck(spacing, segmentationSpacing)
    ) {
      baseVolumeIdx = i;
    }
 
    // prepare information used in callback functions
    const lower = thresholdVolumeInformation[i].lower;
    const upper = thresholdVolumeInformation[i].upper;
 
    volumeInfoList.push({
      imageData,
      lower,
      upper,
      spacing,
      dimensions,
      volumeSize,
      voxelManager,
    });
  }
 
  return {
    volumeInfoList,
    baseVolumeIdx,
  };
}
 
const segmentIndicesCache = new Map<
  string,
  { indices: number[]; isDirty: boolean }
>();
 
export const setSegmentationDirty = (segmentationId: string) => {
  const cached = segmentIndicesCache.get(segmentationId);
  Iif (cached) {
    cached.isDirty = true;
  }
};
 
export const setSegmentationClean = (segmentationId: string) => {
  const cached = segmentIndicesCache.get(segmentationId);
  if (cached) {
    cached.isDirty = false;
  }
};
 
export const getCachedSegmentIndices = (segmentationId: string) => {
  const cached = segmentIndicesCache.get(segmentationId);
  if (cached && !cached.isDirty) {
    return cached.indices;
  }
  return null;
};
 
export const setCachedSegmentIndices = (
  segmentationId: string,
  indices: number[]
) => {
  segmentIndicesCache.set(segmentationId, { indices, isDirty: false });
};