/// <summary> /// Adds an inner contour to the present object. The inner contour should contain /// the points on the inner rim of the region of interest (innermost points that are /// still foreground). /// </summary> /// <param name="inner">The points on the inner rim of the region, surrounding any "holes".</param> public void AddInnerContour(PolygonPoints inner) { Inner.Add(inner ?? throw new ArgumentNullException(nameof(inner))); TotalPixels -= inner.VoxelCounts.Total; }
/// <summary> /// Extracts polygons by walking the edge of the foreground values of the worker volume. /// This method will return closed polygons. Also, the polygons will be filled (any holes removed). /// The <paramref name="foundPolygons"/> argument will be updated in place, by marking all voxels /// that are found to be inside of a polygon with the index of that polygon. /// The first new polygon that is found will be given the number supplied in <paramref name="firstNewPolygon"/> /// (must be 1 or higher) /// </summary> /// <param name="binaryVolume">The volume we are extracting polygons from. Must only contain values 0 and 1.</param> /// <param name="foundPolygons">A volume that contains the IDs for all polygons that have been found already. 0 means no polygon found.</param> /// <param name="searchInsidePolygon">For walking along edges, limit to the voxels that presently are assigned to /// the polygon given here.</param> /// <param name="isInnerPolygon">If true, the polygon will walk along boundaries around regions with /// voxel value 0 (background), but keep the polyon points on voxel values 1, counterclockwises.</param> /// <param name="firstNewPolygon">The ID for the first polygon that is found.</param> /// <returns>A collection of polygons and there respective sizes (number of foreground points in each polygon)</returns> private static Dictionary <ushort, PolygonPoints> ExtractPolygons( Volume2D <byte> binaryVolume, ushort[] foundPolygons, ushort searchInsidePolygon, bool isInnerPolygon, ushort firstNewPolygon) { if (binaryVolume == null) { throw new ArgumentNullException(nameof(binaryVolume)); } if (foundPolygons == null) { throw new ArgumentNullException(nameof(foundPolygons)); } if (firstNewPolygon < 1) { throw new ArgumentOutOfRangeException(nameof(firstNewPolygon), "Polygon index 0 is reserved for 'not assigned'"); } var foregroundId = isInnerPolygon ? (byte)0 : (byte)1; var polygons = new Dictionary <ushort, PolygonPoints>(); var dimX = binaryVolume.DimX; var dimY = binaryVolume.DimY; var volumeArray = binaryVolume.Array; for (var y = 0; y < dimY; y++) { // Manually computing index, rather than relying on GetIndex, brings substantial speedup. var offsetY = y * dimX; for (var x = 0; x < dimX; x++) { var pixelIndex = x + offsetY; // Starting point of a new polygon is where we see the desired foreground in the original // volume, and have either not found any polygon yet (searchInsidePolygon == 0) // or have found a polygon already and now search for the holes inside it. if (volumeArray[pixelIndex] == foregroundId && foundPolygons[pixelIndex] == searchInsidePolygon) { PointInt startPoint; if (isInnerPolygon) { Debug.Assert(y >= 1, "When searching for innner polygons (holes), expecting that there is foreground in the row above."); startPoint = new PointInt(x, y - 1); } else { startPoint = new PointInt(x, y); } VoxelCounts voxelCounts; PointInt[] contourPoints; if (isInnerPolygon) { var innerPoints = FindPolygon( binaryVolume, foundPolygons, searchInsidePolygon, new PointInt(x, y), backgroundId: 1, searchClockwise: true); voxelCounts = FillPolygon.FillPolygonAndCount( innerPoints, foundPolygons, firstNewPolygon, binaryVolume, foregroundId: 0); contourPoints = FindPolygon( binaryVolume, foundPolygons, searchInsidePolygon, startPoint, backgroundId: 0, searchClockwise: false); } else { contourPoints = FindPolygon( binaryVolume, foundPolygons, searchInsidePolygon, startPoint, backgroundId: 0, searchClockwise: true); voxelCounts = FillPolygon.FillPolygonAndCount( contourPoints, foundPolygons, firstNewPolygon, binaryVolume, foregroundId); } var polygon = new PolygonPoints( contourPoints, voxelCounts, searchInsidePolygon, isInside: isInnerPolygon, startPointMinimumY: startPoint); polygons.Add(firstNewPolygon, polygon); firstNewPolygon++; } } } return(polygons); }
/// <summary> /// Creates a new instance of the class. /// </summary> /// <param name="outer">The points that make up the outer rim of the region, traversed clockwise.</param> public InnerOuterPolygon(PolygonPoints outer) { Outer = outer ?? throw new ArgumentNullException(nameof(outer)); Inner = new List <PolygonPoints>(); TotalPixels = outer.VoxelCounts.Total; }