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

100% Statements 1/1
100% Branches 0/0
100% Functions 0/0
100% Lines 1/1

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  428x                                                                                                                                                                                                                                                                                                        
// TODO: add support for WGSL files
const shader = `
const MAX_STRENGTH = 65535f;
 
// Workgroup size - X*Y*Z must be multiple of 32 for better performance
override workGroupSizeX = 1u;
override workGroupSizeY = 1u;
override workGroupSizeZ = 1u;
 
// Compare the current voxel to neighbors using a 9x9x9 window
override windowSize = 9i;
 
struct Params {
  size: vec3u,
  iteration: u32,
}
 
// New structure to track bounds of modified voxels
struct Bounds {
  minX: atomic<i32>,
  minY: atomic<i32>,
  minZ: atomic<i32>,
  maxX: atomic<i32>,
  maxY: atomic<i32>,
  maxZ: atomic<i32>,
}
 
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage> volumePixelData: array<f32>;
@group(0) @binding(2) var<storage, read_write> labelmap: array<u32>;
@group(0) @binding(3) var<storage, read_write> strengthData: array<f32>;
@group(0) @binding(4) var<storage> prevLabelmap: array<u32>;
@group(0) @binding(5) var<storage> prevStrengthData: array<f32>;
@group(0) @binding(6) var<storage, read_write> updatedVoxelsCounter: array<atomic<u32>>;
@group(0) @binding(7) var<storage, read_write> modifiedBounds: Bounds;
 
fn getPixelIndex(ijkPos: vec3u) -> u32 {
  let numPixelsPerSlice = params.size.x * params.size.y;
  return ijkPos.x + ijkPos.y * params.size.x + ijkPos.z * numPixelsPerSlice;
}
 
fn updateBounds(position: vec3i) {
  // Atomically update min bounds (use min operation)
  let oldMinX = atomicMin(&modifiedBounds.minX, position.x);
  let oldMinY = atomicMin(&modifiedBounds.minY, position.y);
  let oldMinZ = atomicMin(&modifiedBounds.minZ, position.z);
 
  // Atomically update max bounds (use max operation)
  let oldMaxX = atomicMax(&modifiedBounds.maxX, position.x);
  let oldMaxY = atomicMax(&modifiedBounds.maxY, position.y);
  let oldMaxZ = atomicMax(&modifiedBounds.maxZ, position.z);
}
 
@compute @workgroup_size(workGroupSizeX, workGroupSizeY, workGroupSizeZ)
fn main(
  @builtin(global_invocation_id) globalId: vec3u,
) {
  // Make sure it will not get out of bounds for volume with sizes that
  // are not multiple of workGroupSize
  if (
    globalId.x >= params.size.x ||
    globalId.y >= params.size.y ||
    globalId.z >= params.size.z
  ) {
    return;
  }
 
  // Initialize bounds for the first iteration
  if (params.iteration == 0 && globalId.x == 0 && globalId.y == 0 && globalId.z == 0) {
    // Initialize to opposite extremes to ensure any update will improve the bounds
    atomicStore(&modifiedBounds.minX, i32(params.size.x));
    atomicStore(&modifiedBounds.minY, i32(params.size.y));
    atomicStore(&modifiedBounds.minZ, i32(params.size.z));
    atomicStore(&modifiedBounds.maxX, -1);
    atomicStore(&modifiedBounds.maxY, -1);
    atomicStore(&modifiedBounds.maxZ, -1);
  }
 
  let currentCoord = vec3i(globalId);
  let currentPixelIndex = getPixelIndex(globalId);
 
  let numPixels = arrayLength(&volumePixelData);
  let currentPixelValue = volumePixelData[currentPixelIndex];
 
  if (params.iteration == 0) {
    // All non-zero initial labels are given maximum strength
    strengthData[currentPixelIndex] = select(MAX_STRENGTH, 0., labelmap[currentPixelIndex] == 0);
 
    // Update bounds for non-zero initial labels
    if (labelmap[currentPixelIndex] != 0) {
      updateBounds(currentCoord);
    }
    return;
  }
 
  // It should at least copy the values from previous state
  var newLabel = prevLabelmap[currentPixelIndex];
  var newStrength = prevStrengthData[currentPixelIndex];
 
  let window = i32(ceil(f32(windowSize - 1) * .5));
  let minWindow = -1i * window;
  let maxWindow = 1i * window;
 
  for (var k = minWindow; k <= maxWindow; k++) {
    for (var j = minWindow; j <= maxWindow; j++) {
      for (var i = minWindow; i <= maxWindow; i++) {
        // Skip current voxel
        if (i == 0 && j == 0 && k == 0) {
          continue;
        }
 
        let neighborCoord = currentCoord + vec3i(i, j, k);
 
        //  Boundary conditions. Do not grow outside of the volume
        if (
          neighborCoord.x < 0i || neighborCoord.x >= i32(params.size.x) ||
          neighborCoord.y < 0i || neighborCoord.y >= i32(params.size.y) ||
          neighborCoord.z < 0i || neighborCoord.z >= i32(params.size.z)
        ) {
          continue;
        }
 
        let neighborIndex = getPixelIndex(vec3u(neighborCoord));
        let neighborPixelValue = volumePixelData[neighborIndex];
        let prevNeighborStrength = prevStrengthData[neighborIndex];
        let strengthCost = abs(neighborPixelValue - currentPixelValue);
        let takeoverStrength = prevNeighborStrength - strengthCost;
 
        if (takeoverStrength > newStrength) {
          newLabel = prevLabelmap[neighborIndex];
          newStrength = takeoverStrength;
        }
      }
    }
  }
 
  if (labelmap[currentPixelIndex] != newLabel) {
    atomicAdd(&updatedVoxelsCounter[params.iteration], 1u);
 
    // Update bounds for modified voxels
    updateBounds(currentCoord);
  }
 
  labelmap[currentPixelIndex] = newLabel;
  strengthData[currentPixelIndex] = newStrength;
}
`;
 
export default shader;