All files / tools/src/utilities/contours findContourHoles.ts

0% Statements 0/24
0% Branches 0/10
0% Functions 0/5
0% Lines 0/21

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                                                                                                                                                                         
import type { Types } from '@cornerstonejs/core';
import getSignedArea from '../math/polyline/getSignedArea';
import containsPoints from '../math/polyline/containsPoints';
import isClosed from '../math/polyline/isClosed';
 
/**
 * Result interface for hole detection
 */
export interface ContourHoleDetectionResult {
  /** Index of the polyline that contains holes */
  contourIndex: number;
  /** Indexes of the polylines that are holes within this contour */
  holeIndexes: number[];
}
 
/**
 * Checks if one polygon is completely inside another polygon
 */
function isPolygonInsidePolygon(
  inner: Types.Point2[],
  outer: Types.Point2[]
): boolean {
  // Check if all points of inner polygon are inside outer polygon
  return containsPoints(outer, inner);
}
 
/**
 * Detects holes in contours from an array of polylines
 *
 * @param polylines - Array of polylines, where each polyline is an array of Point2
 * @returns Array of ContourHoleDetectionResult containing contour indexes and their hole indexes
 */
export default function findContourHoles(
  polylines: Types.Point2[][]
): ContourHoleDetectionResult[] {
  const results: ContourHoleDetectionResult[] = [];
 
  // Filter only closed polylines and keep track of their original indexes
  const closedPolylines: { polyline: Types.Point2[]; originalIndex: number }[] =
    [];
 
  polylines.forEach((polyline, index) => {
    if (isClosed(polyline)) {
      closedPolylines.push({ polyline, originalIndex: index });
    }
  });
 
  // For each closed polyline, check if other closed polylines are holes inside it
  for (let i = 0; i < closedPolylines.length; i++) {
    const outerContour = closedPolylines[i];
    const outerArea = Math.abs(getSignedArea(outerContour.polyline));
    const holeIndexes: number[] = [];
 
    for (let j = 0; j < closedPolylines.length; j++) {
      if (i === j) {
        continue;
      } // Skip self
 
      const potentialHole = closedPolylines[j];
      const holeArea = Math.abs(getSignedArea(potentialHole.polyline));
 
      // A hole should be smaller than the outer contour and completely inside it
      if (
        holeArea < outerArea &&
        isPolygonInsidePolygon(potentialHole.polyline, outerContour.polyline)
      ) {
        holeIndexes.push(potentialHole.originalIndex);
      }
    }
 
    // Only add to results if this contour has holes
    if (holeIndexes.length > 0) {
      results.push({
        contourIndex: outerContour.originalIndex,
        holeIndexes: holeIndexes.sort((a, b) => a - b), // Sort hole indexes
      });
    }
  }
 
  // Sort results by contour index for consistent output
  return results.sort((a, b) => a.contourIndex - b.contourIndex);
}
 
export { findContourHoles };