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);