private void btnOpenAsImage_Click(object sender, EventArgs e)
        {
            // repaint
            RaisePaintEvent(null, null);
            Image <Bgr, byte> currShowingImage = currImage.Copy();

            ServiceTools.ShowPicture(currShowingImage, "");
        }
        public void Clusterize()
        {
            ArithmeticsOnImages aoi = new ArithmeticsOnImages();

            aoi.dmY        = dmDensityMesh;
            aoi.ExprString = "grad5p(Y)";
            aoi.RPNeval(true);
            List <DenseMatrix> lDMGradField = aoi.lDMRes;


            DenseMatrix dmMask = dmDensityMesh.Copy();

            dmMask.MapIndexedInplace((r, c, dVal) =>
            {
                // r = y - perc5
                // c = x - median
                Point currPt = new Point(c, r);
                return((conditionOnPoints(currPt)) ? (1.0d) : (0.0d));
                //if (r > c) return 0.0d;
                //else return 1.0d;
            });
            Image <Gray, Byte> imgMask = ImageProcessing.grayscaleImageFromDenseMatrixWithFixedValuesBounds(dmMask, 0.0d, 1.0d);

            // imgMask = imgMask.Flip(FlipType.Vertical);
            imgMask = imgMask.Flip(FLIP.VERTICAL);



            // отфильтровать малые значения - ?

            // выделить классы

            List <ConnectedObjectsAtASlice> lSlicesData = new List <ConnectedObjectsAtASlice>();
            double dthresholdingMaxValue = dmDensityMesh.Values.Max();
            //double dthresholdingMinValue = dmSmoothed.Values.Min();
            double dthresholdingMinValue = 0.0d;
            double dthresholdingDiscrete = (dthresholdingMaxValue - dthresholdingMinValue) / 30.0d;

            for (double dThresholding = dthresholdingMaxValue; dThresholding > dthresholdingMinValue - dthresholdingDiscrete; dThresholding -= dthresholdingDiscrete)
            {
                ConnectedObjectsAtASlice corrSliceObj = new ConnectedObjectsAtASlice(dmDensityMesh, dmDensityMeshXcoord,
                                                                                     dmDensityMeshYcoord, dThresholding);
                corrSliceObj.DetectConnectedObjects();
                //ServiceTools.ShowPicture(corrSliceObj.previewImage, "thresholding value = " + dThresholding.ToString("e"));
                lSlicesData.Add(corrSliceObj);
            }


            ConnectedObjectsAtASlice prevSlice = lSlicesData[0];

            foundClassesContours.AddRange(prevSlice.edgeContoursList);

            foreach (ConnectedObjectsAtASlice currSlice in lSlicesData)
            {
                if (lSlicesData.IndexOf(currSlice) == 0)
                {
                    continue;                                      // самый верхний пропускаем
                }
                //List<Tuple<Contour<Point>, Contour<Point>>> currSliceCoveringContours =
                //    new List<Tuple<Contour<Point>, Contour<Point>>>();
                List <Tuple <Contour <Point>, Contour <Point> > > currSliceCoveringContours =
                    new List <Tuple <Contour <Point>, Contour <Point> > >();
                //item1 - внутренний, из предыдущего слайса
                //item2 - внешний, из текущего слайса

                foreach (Contour <Point> caughtCont in foundClassesContours)
                {
                    Contour <Point> coveringCaughtCont = currSlice.FindContourContainingSample(caughtCont);
                    currSliceCoveringContours.Add(new Tuple <Contour <Point>, Contour <Point> >(caughtCont,
                                                                                                coveringCaughtCont));
                }

                // добавим контуры, которые только что появились и раньше не были видны на срезах
                // но только если количество допустимых клатеров еще позволяет
                // Иначе - будем ждать, когда они вольются в в какой-нибудь из вновь расширившихся контуров
                foreach (Contour <Point> newContour in currSlice.edgeContoursList)
                {
                    if ((currSliceCoveringContours.Find(tpl => (tpl.Item2 == newContour)) == null) && (currSliceCoveringContours.Count() < maxClustersCount))
                    {
                        currSliceCoveringContours.Add(new Tuple <Contour <Point>, Contour <Point> >(newContour, newContour));
                    }
                }

                // что делать, если какой-нибудь новый контур покрывает больше одного предыдущего
                List <IGrouping <Contour <Point>, Tuple <Contour <Point>, Contour <Point> > > > groups =
                    new List <IGrouping <Contour <Point>, Tuple <Contour <Point>, Contour <Point> > > >
                        (currSliceCoveringContours.GroupBy(tpl => tpl.Item2));
                if (groups.Count(grp => (grp.Count() > 1)) > 0)
                {
                    // есть контуры текущего среза, которые содержат более одного контура предыдущего среза
                    foreach (IGrouping <Contour <Point>, Tuple <Contour <Point>, Contour <Point> > > currGroup in groups)
                    {
                        if (currGroup.Count() == 1)
                        {
                            Tuple <Contour <Point>, Contour <Point> > contourTuple = currGroup.First();
                            foundClassesContours.Remove(contourTuple.Item1);
                            foundClassesContours.Add(contourTuple.Item2);
                        }
                        else
                        {
                            // currGroup - группа кортежей контуров, где
                            //              item1 - внутренний, из предыдущего слайса
                            //              item2 - внешний, из текущего слайса
                            // надо точки, которые лежат вне контуров предыдущего слайса отнести к "своим" контурам
                            // попробуем по направлению градиента - относить точку к тому контуру, на который укажет вектор градиента
                            Contour <Point> currCoveringContour = currGroup.Key; // item2 - внешний, из текущего слайса - см.строку группировки



                            Rectangle currCoveringContourBoundingRectangle = currCoveringContour.BoundingRectangle;

                            Image <Gray, byte> tmpImg1 =
                                new Image <Gray, byte>(new Size(currCoveringContourBoundingRectangle.Right,
                                                                currCoveringContourBoundingRectangle.Bottom));
                            tmpImg1.Draw(currCoveringContour, white, -1);
                            foreach (Tuple <Contour <Point>, Contour <Point> > tpl in currGroup)
                            {
                                Contour <Point>    excludingCntr = tpl.Item1;
                                Image <Gray, byte> tmpExcl       = tmpImg1.CopyBlank();
                                tmpExcl.Draw(excludingCntr, white, -1);
                                tmpImg1 = tmpImg1 - tmpExcl;
                            }
                            // в картинке tmpImg1 закрашенными остались только точки, которые надо классифицировать
                            List <Point> lPointsToClassify = new List <Point>();

                            for (int x = 0; x < tmpImg1.Width; x++)
                            {
                                for (int y = 0; y < tmpImg1.Height; y++)
                                {
                                    Point currPt = new Point(x, y);
                                    if (tmpImg1[currPt].Equals(white))
                                    {
                                        lPointsToClassify.Add(currPt);
                                    }
                                }
                            }

                            List <List <Point> > llArraysOfPointsAdding = new List <List <Point> >();
                            foreach (Tuple <Contour <Point>, Contour <Point> > tpl in currGroup)
                            {
                                llArraysOfPointsAdding.Add(new List <Point>());
                            }

                            List <Contour <Point> > lContoursOfTheCurrGroup =
                                (new List <Tuple <Contour <Point>, Contour <Point> > >(currGroup.ToArray())).ConvertAll(
                                    tpl => tpl.Item1);
                            List <PointD>   lPtdMassCenters       = lContoursOfTheCurrGroup.ConvertAll(cntr => cntr.MassCenter());
                            Contour <Point> themassCentersPolygon = new Contour <Point>(new MemStorage());
                            themassCentersPolygon.PushMulti(lPtdMassCenters.ConvertAll <Point>(ptd => ptd.Point()).ToArray(),
                                                            BACK_OR_FRONT.BACK);
                            //themassCentersPolygon.Push(lPtdMassCenters.ConvertAll<Point>(ptd => ptd.Point()).ToArray());
                            Image <Gray, byte> tmpImg = imgMask.CopyBlank();
                            tmpImg.Draw(themassCentersPolygon, white, -1);
                            themassCentersPolygon = tmpImg.DetectContours()[0];



                            foreach (Point currPtToClassify in lPointsToClassify)
                            {
                                int cntrToAddPointTo = AttachPointToOneOfConcurrentContours(
                                    lContoursOfTheCurrGroup,
                                    lPtdMassCenters,
                                    themassCentersPolygon,
                                    currPtToClassify,
                                    lDMGradField);
                                if (cntrToAddPointTo == -1)
                                {
                                    continue;
                                }
                                else
                                {
                                    llArraysOfPointsAdding[cntrToAddPointTo].Add(currPtToClassify);
                                }
                            }
                            // распределили. теперь надо сформировать новые контуры - с учетом добавленных точек.
                            List <Image <Gray, byte> > lImagesToDetectNewContours = new List <Image <Gray, byte> >();
                            foreach (Tuple <Contour <Point>, Contour <Point> > tpl in currGroup)
                            {
                                Image <Gray, byte> tmpImgCurrCont = tmpImg1.CopyBlank();
                                tmpImgCurrCont.Draw(tpl.Item1, white, -1);
                                lImagesToDetectNewContours.Add(tmpImgCurrCont);
                            }


                            for (int cntIdx = 0; cntIdx < currGroup.Count(); cntIdx++)
                            {
                                foreach (Point pt in llArraysOfPointsAdding[cntIdx])
                                {
                                    lImagesToDetectNewContours[cntIdx][pt.Y, pt.X] = white;
                                }

                                #region // obsolete
                                //Contour<Point> cnt1 =
                                //    lImagesToDetectNewContours[cntIdx].FindContours(
                                //        Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
                                //        Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_LIST);
                                //List<Contour<Point>> lTmpCtrs = new List<Contour<Point>>();
                                //while (true)
                                //{
                                //    lTmpCtrs.Add(cnt1);
                                //    cnt1 = cnt1.HNext;
                                //    if (cnt1 == null)
                                //        break;
                                //}
                                #endregion // obsolete

                                ////найдем наибольший из получившихся контуров
                                List <Contour <Point> > lTmpCtrs = lImagesToDetectNewContours[cntIdx].DetectContours();

                                foundClassesContours.Remove(currGroup.ElementAt(cntIdx).Item1);
                                double maxArea             = lTmpCtrs.Max(cntr => cntr.Area);
                                int    idxOfMaxAreaContour = lTmpCtrs.FindIndex(cntr => cntr.Area >= maxArea);
                                foundClassesContours.Add(lTmpCtrs[idxOfMaxAreaContour]);
                            }
                        }
                    }
                }
                else
                {
                    foreach (Tuple <Contour <Point>, Contour <Point> > contourTuple in currSliceCoveringContours)
                    {
                        foundClassesContours.Remove(contourTuple.Item1);
                        foundClassesContours.Add(contourTuple.Item2);
                    }
                }
                //theLogWindow = ServiceTools.LogAText(theLogWindow,
                //    "processing thresholding value = " + currSlice.slicingThresholdingValue, true);
            }


            //theLogWindow = ServiceTools.LogAText(theLogWindow,
            //        Environment.NewLine +
            //        "========" + Environment.NewLine +
            //        "FINISHED" + Environment.NewLine +
            //        "========" + Environment.NewLine, true);

            Image <Gray, Byte> imgDataBinary = ImageProcessing.grayscaleImageFromDenseMatrixWithFixedValuesBounds(dmDensityMesh, 0.0d, 1.0d);
            Image <Bgr, byte>  previewImage  = imgDataBinary.CopyBlank().Convert <Bgr, Byte>();
            var colorGen = new RandomPastelColorGenerator();
            foreach (Contour <Point> currCntr in foundClassesContours)
            {
                Color currentColor    = colorGen.GetNext();
                var   currentColorBgr = new Bgr(currentColor);
                previewImage.Draw(currCntr, currentColorBgr, -1);
            }
            previewImage = previewImage.And(imgMask.Convert <Bgr, byte>());
            ServiceTools.ShowPicture(previewImage, "");
        }