All files / packages/tools/src/utilities/contours generateContourSetsFromLabelmap.ts

1.51% Statements 1/66
0% Branches 0/15
0% Functions 0/2
1.66% Lines 1/60

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                  1x                                                                                                                                                                                                                                                                                            
import { cache as cornerstoneCache } from '@cornerstonejs/core';
import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchingSquares';
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
 
import { getDeduplicatedVTKPolyDataPoints } from '../contours';
import { findContoursFromReducedSet } from './contourFinder';
import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
 
const { Labelmap } = SegmentationRepresentations;
 
function generateContourSetsFromLabelmap({ segmentations }) {
  const { representationData, segments = [0, 1] } = segmentations;
  const { volumeId: segVolumeId } = representationData[Labelmap];
 
  // Get segmentation volume
  const vol = cornerstoneCache.getVolume(segVolumeId);
  if (!vol) {
    console.warn(`No volume found for ${segVolumeId}`);
    return;
  }
 
  const numSlices = vol.dimensions[2];
 
  // NOTE: Workaround for marching squares not finding closed contours at
  // boundary of image volume, clear pixels along x-y border of volume
  const segData = vol.imageData.getPointData().getScalars().getData();
  const pixelsPerSlice = vol.dimensions[0] * vol.dimensions[1];
 
  for (let z = 0; z < numSlices; z++) {
    for (let y = 0; y < vol.dimensions[1]; y++) {
      const index = y * vol.dimensions[0] + z * pixelsPerSlice;
      segData[index] = 0;
      segData[index + vol.dimensions[0] - 1] = 0;
    }
  }
 
  // end workaround
  //
  //
  const ContourSets = [];
 
  const { FrameOfReferenceUID } = vol.metadata;
  // Iterate through all segments in current segmentation set
  const numSegments = segments.length;
  for (let segIndex = 0; segIndex < numSegments; segIndex++) {
    const segment = segments[segIndex];
 
    // Skip empty segments
    if (!segment) {
      continue;
    }
 
    const sliceContours = [];
    const scalars = vtkDataArray.newInstance({
      name: 'Scalars',
      numberOfComponents: 1,
      size: pixelsPerSlice * numSlices,
      dataType: 'Uint8Array',
    });
    const { containedSegmentIndices } = segment;
    for (let sliceIndex = 0; sliceIndex < numSlices; sliceIndex++) {
      // Check if the slice is empty before running marching cube
      if (
        isSliceEmptyForSegment(sliceIndex, segData, pixelsPerSlice, segIndex)
      ) {
        continue;
      }
      const frameStart = sliceIndex * pixelsPerSlice;
 
      try {
        // Modify segData for this specific segment directly
        for (let i = 0; i < pixelsPerSlice; i++) {
          const value = segData[i + frameStart];
          if (value === segIndex || containedSegmentIndices?.has(value)) {
            (scalars as any).setValue(i + frameStart, 1);
          } else {
            (scalars as any).setValue(i, 0);
          }
        }
 
        const mSquares = vtkImageMarchingSquares.newInstance({
          slice: sliceIndex,
        });
 
        // filter out the scalar data so that only it has background and
        // the current segment index
        const imageDataCopy = vtkImageData.newInstance();
 
        imageDataCopy.shallowCopy(vol.imageData);
        imageDataCopy.getPointData().setScalars(scalars);
 
        // Connect pipeline
        mSquares.setInputData(imageDataCopy);
        const cValues = [1];
        mSquares.setContourValues(cValues);
        mSquares.setMergePoints(false);
 
        // Perform marching squares
        const msOutput = mSquares.getOutputData();
 
        // Clean up output from marching squares
        const reducedSet = getDeduplicatedVTKPolyDataPoints(msOutput);
        if (reducedSet.points?.length) {
          const contours = findContoursFromReducedSet(reducedSet.lines);
 
          sliceContours.push({
            contours,
            polyData: reducedSet,
            FrameNumber: sliceIndex + 1,
            sliceIndex,
            FrameOfReferenceUID,
          });
        }
      } catch (e) {
        console.warn(sliceIndex);
        console.warn(e);
      }
    }
 
    const metadata = {
      FrameOfReferenceUID,
    };
 
    const ContourSet = {
      label: segment.label,
      color: segment.color,
      metadata,
      sliceContours,
    };
 
    ContourSets.push(ContourSet);
  }
 
  return ContourSets;
}
 
function isSliceEmptyForSegment(sliceIndex, segData, pixelsPerSlice, segIndex) {
  const startIdx = sliceIndex * pixelsPerSlice;
  const endIdx = startIdx + pixelsPerSlice;
 
  for (let i = startIdx; i < endIdx; i++) {
    if (segData[i] === segIndex) {
      return false;
    }
  }
 
  return true;
}
 
export { generateContourSetsFromLabelmap };