private static ContoursPerSlice GenerateContoursPerSlice( Volume3D <byte> volume, bool fillContours, byte foregroundId, SliceType sliceType, bool filterEmptyContours, Region3D <int> regionOfInterest, ContourSmoothingType axialSmoothingType) { var region = regionOfInterest ?? new Region3D <int>(0, 0, 0, volume.DimX - 1, volume.DimY - 1, volume.DimZ - 1); int startPoint; int endPoint; // Only smooth the output on the axial slices var smoothingType = axialSmoothingType; switch (sliceType) { case SliceType.Axial: startPoint = region.MinimumZ; endPoint = region.MaximumZ; break; case SliceType.Coronal: startPoint = region.MinimumY; endPoint = region.MaximumY; smoothingType = ContourSmoothingType.None; break; case SliceType.Sagittal: startPoint = region.MinimumX; endPoint = region.MaximumX; smoothingType = ContourSmoothingType.None; break; default: throw new ArgumentOutOfRangeException(nameof(sliceType), sliceType, null); } var numberOfSlices = endPoint - startPoint + 1; var arrayOfContours = new Tuple <int, IReadOnlyList <ContourPolygon> > [numberOfSlices]; for (var i = 0; i < arrayOfContours.Length; i++) { var z = startPoint + i; var volume2D = ExtractSlice.Slice(volume, sliceType, z); var contours = fillContours ? ContoursFilled(volume2D, foregroundId, smoothingType): ContoursWithHoles(volume2D, foregroundId, smoothingType); arrayOfContours[i] = Tuple.Create(z, contours); } return(new ContoursPerSlice( arrayOfContours .Where(x => !filterEmptyContours || x.Item2.Count > 0) .ToDictionary(x => x.Item1, x => x.Item2))); }
/// <summary> /// We current have the following approaches: /// - None: Takes the mask representation of the polygon and return the outer edge /// - Small: Uses a simplistic 'code-book' approach to smoothing the outer edge of the polygon /// </summary> /// <param name="polygon">The input mask representation of the extracted polygon.</param> /// <param name="smoothingType">The smoothing type to use.</param> /// <returns>The smoothed polygon.</returns> public static PointF[] Smooth(InnerOuterPolygon polygon, ContourSmoothingType smoothingType = ContourSmoothingType.Small) { var result = SmoothAndMerge( polygon, (points, isCounterClockwise) => SmoothPoints(points, isCounterClockwise, smoothingType)); return(ContourSimplifier.RemoveRedundantPoints(result)); }
/// <summary> /// Extracts contours from all slices of the given volume, searching for the given foreground value. /// Contour extraction will take holes into account. /// </summary> /// <param name="volume"></param> /// <param name="foregroundId">The voxel value that should be used in the contour search as foreground.</param> /// <param name="axialSmoothingType">The smoothing that should be applied when going from a point polygon to /// contours. This will only affect axial slice, for other slice types no smoothing will be applied. /// <param name="sliceType">The type of slice that should be used for contour extraction.</param> /// <param name="filterEmptyContours">If true, contours with no points are not extracted.</param> /// <param name="regionOfInterest"></param> /// <returns></returns> public static ContoursPerSlice ContoursWithHolesPerSlice( this Volume3D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, SliceType sliceType = SliceType.Axial, bool filterEmptyContours = true, Region3D <int> regionOfInterest = null, ContourSmoothingType axialSmoothingType = ContourSmoothingType.Small) => ExtractContours.ContoursWithHolesPerSlice(volume, foregroundId, sliceType, filterEmptyContours, regionOfInterest, axialSmoothingType);
/// <summary> /// Extracts contours from all slices of the given volume, searching for the given foreground value. /// Contour extraction will take holes into account. /// </summary> /// <param name="volume"></param> /// <param name="foregroundId">The voxel value that should be used in the contour search as foreground.</param> /// <param name="axialSmoothingType">The smoothing that should be applied when going from a point polygon to /// contours. This will only affect axial slice, for other slice types no smoothing will be applied. /// <param name="sliceType">The type of slice that should be used for contour extraction.</param> /// <param name="filterEmptyContours">If true, contours with no points are not extracted.</param> /// <param name="regionOfInterest"></param> /// <returns></returns> public static ContoursPerSlice ContoursFilledPerSlice( Volume3D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, SliceType sliceType = SliceType.Axial, bool filterEmptyContours = true, Region3D <int> regionOfInterest = null, ContourSmoothingType axialSmoothingType = ContourSmoothingType.Small) { return(GenerateContoursPerSlice(volume, true, foregroundId, sliceType, filterEmptyContours, regionOfInterest, axialSmoothingType)); }
/// <summary> /// Takes an input volume and extracts the contours for all voxels that have the given /// foreground value. /// Contour extraction will take account of holes and inserts, up to the default nesting level. /// </summary> /// <param name="volume">The input volume.</param> /// <param name="foregroundId">The ID we are looking for when extracting contours.</param> /// <param name="smoothingType">The type of smoothing that should be applied when going from a /// point polygon to a contour.</param> /// <param name="maxNestingLevel">The maximum nesting level up to which polygons should be extracted. If set to /// 0, only the outermost polygons will be returned. If 1, the outermost polygons and the holes therein. /// If 2, the outermost polygon, the holes, and the foreground inside the holes.</param> /// <returns>The collection of contours.</returns> public static IReadOnlyList <ContourPolygon> ContoursWithHoles(Volume2D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, ContourSmoothingType smoothingType = ContourSmoothingType.Small, int maxNestingLevel = DefaultMaxPolygonNestingLevel) { var polygonPoints = PolygonsWithHoles(volume, foregroundId, maxNestingLevel); return(polygonPoints .Select(x => new ContourPolygon(SmoothPolygon.Smooth(x, smoothingType), x.TotalPixels)) .ToList()); }
/// <summary> /// Takes an input volume and extracts the contours for all voxels that have the given /// foreground value. /// Contour extraction will not take account of holes, and hence only return the outermost /// contour around a region of interest. /// </summary> /// <param name="volume">The input volume.</param> /// <param name="foregroundId">The ID we are looking for when extracting contours.</param> /// <param name="smoothingType">The type of smoothing that should be applied when going from a /// point polygon to a contour.</param> /// <returns>The collection of contours.</returns> public static IReadOnlyList <ContourPolygon> ContoursFilled(Volume2D <byte> volume, byte foregroundId = ModelConstants.MaskForegroundIntensity, ContourSmoothingType smoothingType = ContourSmoothingType.Small) { var polygonPoints = PolygonsFilled(volume, foregroundId); return(polygonPoints .Select(x => { var isCounterClockwise = false; var smoothedPoints = SmoothPolygon.SmoothPoints(x.Points, isCounterClockwise, smoothingType); return new ContourPolygon(smoothedPoints, x.VoxelCounts.Total); }) .ToList()); }
/// <summary> /// Generates a contour that traces the voxels at the given integer position, and that is smoothed /// using the given smoothing level. /// </summary> /// <param name="points">The set of integer points that describe the polygon.</param> /// <param name="isCounterClockwise">If true, the points are an inner contour and are given in CCW order. /// Otherwise, assume they are an outer contour and are in clockwise order.</param> /// <param name="smoothingType">The type of smoothing that should be applied.</param> /// <returns></returns> public static PointF[] SmoothPoints(IReadOnlyList <PointInt> points, bool isCounterClockwise, ContourSmoothingType smoothingType) { switch (smoothingType) { case ContourSmoothingType.None: return(ClockwisePointsToExternalPathWindowsPoints(points, isCounterClockwise, -0.5f)); case ContourSmoothingType.Small: return(SmallSmoothPolygon(points, isCounterClockwise)); default: throw new NotImplementedException($"There is smoothing method for {smoothingType}"); } }
/// <summary> /// Extracts the contours around all voxel values in the volume that have the given foreground value. /// All other voxel values (zero and anything that is not the foreground value) is treated as background. /// Contour extraction will not take account of holes, and hence only return the outermost /// contour around a region of interest. /// </summary> /// <param name="volume"></param> /// <param name="foregroundId">The voxel value that should be used as foreground in the contour search.</param> /// <param name="smoothingType">The smoothing that should be applied when going from a point polygon to /// a contour.</param> /// <returns></returns> public static IReadOnlyList <ContourPolygon> ContoursFilled( this Volume2D <byte> volume, byte foregroundId = 1, ContourSmoothingType smoothingType = ContourSmoothingType.Small) => ExtractContours.ContoursFilled(volume, foregroundId, smoothingType);