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

0% Statements 0/49
0% Branches 0/24
0% Functions 0/7
0% Lines 0/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 147 148 149 150 151 152 153                                                                                                                                                                                                                                                                                                                 
import { Types } from '@cornerstonejs/core';
import { pointInShapeCallback } from '../../utilities';
import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
import { BoundsIJK } from '../../types';
import {
  getVoxelOverlap,
  processVolumes,
  ThresholdInformation,
} from './utilities';
 
export type ThresholdRangeOptions = {
  overwrite: boolean;
  boundsIJK: BoundsIJK;
  overlapType?: number;
  segmentIndex?: number;
};
 
/**
 * It thresholds a segmentation volume based on a set of threshold values with
 * respect to a list of volumes and respective threshold ranges.
 * @param segmentationVolume - the segmentation volume to be modified
 * @param thresholdVolumeInformation - array of objects containing volume data
 * and a range (lower and upper values) to threshold
 * @param options - the options for thresholding
 * As there is a chance the volumes might have different dimensions and spacing,
 * could be the case of no 1 to 1 mapping. So we need to work with the idea of
 * voxel overlaps (1 to many mappings). We consider all intersections valid, to
 * avoid the complexity to calculate a minimum voxel intersection percentage.
 * This function, given a voxel center and spacing, calculates the overlap of
 * the voxel with another volume and range check the voxels in the overlap.
 * Three situations can occur: all voxels pass the range check, some voxels pass
 * or none voxels pass. The overlapType parameter indicates if the user requires
 * all voxels pass (overlapType = 1) or any voxel pass (overlapType = 0)
 *
 * @returns segmented volume
 */
function thresholdVolumeByRange(
  segmentationVolume: Types.IImageVolume,
  thresholdVolumeInformation: ThresholdInformation[],
  options: ThresholdRangeOptions
): Types.IImageVolume {
  const { imageData: segmentationImageData } = segmentationVolume;
  const scalarData = segmentationVolume.getScalarData();
 
  const { overwrite, boundsIJK } = options;
  const overlapType = options?.overlapType || 0;
 
  // set the segmentation to all zeros
  if (overwrite) {
    for (let i = 0; i < scalarData.length; i++) {
      scalarData[i] = 0;
    }
  }
 
  const { baseVolumeIdx, volumeInfoList } = processVolumes(
    segmentationVolume,
    thresholdVolumeInformation
  );
 
  // global variables used in callbackOverlap function
  let overlaps, total, range;
 
  const testOverlapRange = (volumeInfo, voxelSpacing, voxelCenter) => {
    /**
     * This callback function will test all overlaps between a voxel in base
     * volume (the reference for segmentation volume creation) and voxels in other
     * volumes.
     */
    const callbackOverlap = ({ value }) => {
      total = total + 1;
      if (value >= range.lower && value <= range.upper) {
        overlaps = overlaps + 1;
      }
    };
 
    const { imageData, dimensions, lower, upper } = volumeInfo;
 
    const overlapBounds = getVoxelOverlap(
      imageData,
      dimensions,
      voxelSpacing,
      voxelCenter
    );
 
    // reset global variables and setting the range check
    total = 0;
    overlaps = 0;
    range = { lower, upper };
 
    let overlapTest = false;
 
    // check all voxel overlaps
    pointInShapeCallback(imageData, () => true, callbackOverlap, overlapBounds);
 
    if (overlapType === 0) {
      overlapTest = overlaps > 0; // any voxel overlap is accepted
    } else if (overlapType == 1) {
      overlapTest = overlaps === total; // require all voxel overlaps
    }
    return overlapTest;
  };
 
  // range checks a voxel in a volume with same dimension as the segmentation
  const testRange = (volumeInfo, pointIJK) => {
    const { imageData, referenceValues, lower, upper } = volumeInfo;
    const offset = imageData.computeOffsetIndex(pointIJK);
 
    const value = referenceValues[offset];
    if (value <= lower || value >= upper) {
      return false;
    } else {
      return true;
    }
  };
 
  /**
   * This callback function will test all overlaps between a voxel in base
   * volume (the reference for segmentation volume creation) and voxels in other
   * volumes.
   */
  const callback = ({ index, pointIJK, pointLPS }) => {
    let insert = volumeInfoList.length > 0;
    for (let i = 0; i < volumeInfoList.length; i++) {
      // if volume has the same size as segmentation volume, just range check
      if (volumeInfoList[i].volumeSize === scalarData.length) {
        insert = testRange(volumeInfoList[i], pointIJK);
      } else {
        // if not, need to calculate overlaps
        insert = testOverlapRange(
          volumeInfoList[i],
          volumeInfoList[baseVolumeIdx].spacing,
          pointLPS
        );
      }
      if (!insert) {
        break;
      }
    }
 
    if (insert) {
      scalarData[index] = options.segmentIndex || 1;
    }
  };
 
  pointInShapeCallback(segmentationImageData, () => true, callback, boundsIJK);
 
  triggerSegmentationDataModified(segmentationVolume.volumeId);
 
  return segmentationVolume;
}
 
export default thresholdVolumeByRange;