////////////////////////////////////////////////////////////////////////////////
        //
        // Function Name: IVA_Classification_Segmentation
        //
        // Description  : Segments the classification image
        //
        // Parameters   : image                 - Input Image
        //                imageMask             - Segmented image
        //                roi                   - Region of Interest
        //                preprocessingOptions  - Preprocessing options
        //
        // Return Value : None
        //
        ////////////////////////////////////////////////////////////////////////////////
        public static void IVA_Classification_Segmentation(VisionImage image, VisionImage imageMask, Roi roi, ParticleClassifierPreprocessingOptions preprocessingOptions)
        {
            int useExpandedROI = 0;
            RectangleContour boundingBox = roi.GetBoundingRectangle(), expandedBox = roi.GetBoundingRectangle(), reducedBox = roi.GetBoundingRectangle();

            // Local Threshold method uses a kernel size, and the ROI must be larger than the kernel size for the function to work
            // Take care of making the ROI to extract larger if needed for the local threshold case
            if (preprocessingOptions.ThresholdType == ThresholdType.Local)
            {
                // Get the image size
                int xRes, yRes;
                xRes        = image.Width;
                yRes        = image.Height;
                boundingBox = roi.GetBoundingRectangle();

                // Take into account clipping of ROI. Just get ROI that's within bounds of image.
                if (Math.Min(xRes, (boundingBox.Left + boundingBox.Width)) - Math.Max(0, boundingBox.Left) < preprocessingOptions.LocalThresholdOptions.WindowWidth || Math.Min(yRes, (boundingBox.Top + boundingBox.Height)) - Math.Max(0, boundingBox.Top) < preprocessingOptions.LocalThresholdOptions.WindowHeight)
                {
                    // The ROI is smaller than the kernel. Try to expand the kernel in the directions needed to meet the minimum size required by the kernel.


                    int expandX     = (int)(preprocessingOptions.LocalThresholdOptions.WindowWidth / 2);
                    int expandY     = (int)(preprocessingOptions.LocalThresholdOptions.WindowHeight / 2);
                    int leftExpand  = expandX;
                    int rightExpand = expandX;
                    int leftSlack   = (int)boundingBox.Left;
                    int rightSlack  = (int)(xRes - (boundingBox.Left + boundingBox.Width));

                    if (leftExpand > leftSlack)
                    {
                        rightExpand += (leftExpand - leftSlack);
                    }

                    if (rightExpand > rightSlack)
                    {
                        leftExpand += (rightExpand - rightSlack);
                    }

                    int leftOut = (int)boundingBox.Left - leftExpand;
                    if (leftOut < 0)
                    {
                        leftOut = 0;
                    }
                    int rightOut = (int)(boundingBox.Left + boundingBox.Width + rightExpand);
                    if (rightOut > xRes)
                    {
                        rightOut = xRes;
                    }

                    int topExpand    = expandY;
                    int bottomExpand = expandY;
                    int topSlack     = (int)boundingBox.Top;
                    int bottomSlack  = (int)(yRes - (boundingBox.Top + boundingBox.Height));
                    if (topExpand > topSlack)
                    {
                        bottomExpand += (topExpand - topSlack);
                    }
                    if (bottomExpand > bottomSlack)
                    {
                        topExpand += (bottomExpand - bottomSlack);
                    }
                    int topOut = (int)(boundingBox.Top - topExpand);
                    if (topOut < 0)
                    {
                        topOut = 0;
                    }
                    int bottomOut = (int)(boundingBox.Top + boundingBox.Height + bottomExpand);
                    if (bottomOut > yRes)
                    {
                        bottomOut = yRes;
                    }
                    expandedBox.Initialize(leftOut, topOut, rightOut - leftOut, bottomOut - topOut);

                    // Create the reduced Rect so after performing the local threshold, we can reduce the size back to the original ROI dimensions.
                    reducedBox.Initialize(Math.Max(boundingBox.Left - leftOut, 0), Math.Max(boundingBox.Top - topOut, 0), boundingBox.Width + Math.Min(boundingBox.Left, 0), boundingBox.Height + Math.Min(boundingBox.Top, 0));

                    // Set this flag so the image can be reduced after performing the local threshold.
                    useExpandedROI = 1;
                }
            }

            // if Expanded Box hasn't been updated, use the boundingBox passed in to extract.
            if (useExpandedROI == 0)
            {
                expandedBox = boundingBox;
            }


            // Extract the region of interest into the mask image.
            Algorithms.Extract(image, imageMask, expandedBox, 1, 1);

            // Create a temporary ROI that will be used to mask the extracted image, to get rid of
            // the pixels outside of the rotated rectangle.
            Roi tmpROI = new Roi(roi);

            // If the ROI is a rotated rectangle, then compute the new location of the search ROI.
            if ((roi[0].Type == ContourType.RotatedRectangle) && (((RotatedRectangleContour)roi[0].Shape).Angle > 0.01))
            {
                CoordinateSystem baseSystem = new CoordinateSystem();
                baseSystem.Origin.X        = (roi.GetBoundingRectangle().Left < 0 ? 0 : roi.GetBoundingRectangle().Left);
                baseSystem.Origin.Y        = (roi.GetBoundingRectangle().Top < 0 ? 0 : roi.GetBoundingRectangle().Top);
                baseSystem.Angle           = 0;
                baseSystem.AxisOrientation = AxisOrientation.Direct;

                CoordinateSystem newSystem = new CoordinateSystem(new PointContour(0, 0), 0, AxisOrientation.Direct);

                CoordinateTransform transform = new CoordinateTransform(baseSystem, newSystem);

                Algorithms.TransformRoi(tmpROI, transform);
            }

            // Create a temporary image.
            using (VisionImage tmpImageMask = new VisionImage(ImageType.U8, 7))
            {
                double thresholdMin;
                double thresholdMax;

                switch (preprocessingOptions.ThresholdType)
                {
                case ThresholdType.Manual:
                    thresholdMin = preprocessingOptions.ManualThresholdRange.Minimum;
                    thresholdMax = preprocessingOptions.ManualThresholdRange.Maximum;
                    Algorithms.Threshold(imageMask, imageMask, new Range(thresholdMin, thresholdMax), true, 1);
                    break;

                case ThresholdType.Auto:
                    Collection <ThresholdData> thresholdData;
                    thresholdData = Algorithms.AutoThreshold(image, tmpImageMask, 2, preprocessingOptions.AutoThresholdOptions.Method);

                    if (preprocessingOptions.AutoThresholdOptions.ParticleType == ParticleType.Bright)
                    {
                        thresholdMin = (thresholdData[0].Range.Maximum > preprocessingOptions.AutoThresholdOptions.Limits.Minimum ?
                                        thresholdData[0].Range.Maximum : preprocessingOptions.AutoThresholdOptions.Limits.Minimum);
                        thresholdMax = 255;
                    }
                    else
                    {
                        thresholdMin = 0;
                        thresholdMax = (thresholdData[0].Range.Maximum < preprocessingOptions.AutoThresholdOptions.Limits.Maximum ?
                                        thresholdData[0].Range.Maximum : preprocessingOptions.AutoThresholdOptions.Limits.Maximum);
                    }
                    Algorithms.Threshold(imageMask, imageMask, new Range(thresholdMin, thresholdMax), true, 1);
                    break;

                case ThresholdType.Local:
                    LocalThresholdOptions Options = new LocalThresholdOptions(preprocessingOptions.LocalThresholdOptions.ParticleType, preprocessingOptions.LocalThresholdOptions.Method, 1.0, preprocessingOptions.LocalThresholdOptions.WindowWidth, preprocessingOptions.LocalThresholdOptions.WindowHeight);
                    Options.DeviationWeight = preprocessingOptions.LocalThresholdOptions.DeviationWeight;
                    Algorithms.LocalThreshold(imageMask, imageMask, Options);
                    break;

                default:
                    break;
                }

                /// If the expanded ROI was used, reduce it so no particles are found outside requested ROI.
                if (useExpandedROI == 1)
                {
                    Algorithms.Extract(imageMask, imageMask, reducedBox, 1, 1);
                }

                // Cast the image to 8 bit.
                imageMask.Type = ImageType.U8;

                // Eliminates particles that touch the border of the image.
                if (preprocessingOptions.RejectBorder)
                {
                    if ((roi[0].Type == ContourType.RotatedRectangle) && (((RotatedRectangleContour)roi[0].Shape).Angle > 0.01))
                    {
                        // Special case for the rotated rectangle.
                        Algorithms.Label(imageMask, imageMask, Connectivity.Connectivity8);

                        Collection <short> lookupTable = new Collection <short>();
                        lookupTable.Add(0);
                        for (int i = 1; i < 256; i++)
                        {
                            lookupTable.Add(1);
                        }

                        RoiProfileReport roiProfile = Algorithms.RoiProfile(imageMask, tmpROI);

                        for (int i = 0; i < roiProfile.Report.ProfileData.Count; i++)
                        {
                            lookupTable[0] = (short)roiProfile.Report.ProfileData[i];
                        }

                        Algorithms.UserLookup(imageMask, imageMask, lookupTable);
                    }
                    else
                    {
                        Algorithms.RejectBorder(imageMask, imageMask, Connectivity.Connectivity8);
                    }
                }

                // Remove small particles.
                if (preprocessingOptions.NumberOfErosions > 0)
                {
                    Algorithms.RemoveParticle(imageMask, imageMask, preprocessingOptions.NumberOfErosions, SizeToKeep.KeepLarge);
                }

                // If the rectangle is rotated, mask out the areas of the image that are not in the ROI.
                if ((roi[0].Type == ContourType.RotatedRectangle) && (((RotatedRectangleContour)roi[0].Shape).Angle > 0.01))
                {
                    // Perform the mask
                    Algorithms.RoiToMask(tmpImageMask, tmpROI, new PixelValue(255));
                    Algorithms.And(imageMask, tmpImageMask, imageMask);
                }

                // Sets the mask offset.
                imageMask.MaskOffset.X = Math.Max(0, roi.GetBoundingRectangle().Left);
                imageMask.MaskOffset.Y = Math.Max(0, roi.GetBoundingRectangle().Top);
            }
            tmpROI.Dispose();
        }