private static Gray <byte>[,] SpreadOrientations(Gray <byte>[,] quantizedOrientationImage, int neghborhood)
        {
            var destImg = quantizedOrientationImage.CopyBlank();

            using (var uQuantizedOrientationImage = quantizedOrientationImage.Lock())
                using (var uDestImg = destImg.Lock())
                {
                    byte *srcImgPtr  = (byte *)uQuantizedOrientationImage.ImageData;
                    int   imgStride  = uQuantizedOrientationImage.Stride;
                    byte *destImgPtr = (byte *)uDestImg.ImageData;

                    int imgHeight = uDestImg.Height;
                    int imgWidth  = uDestImg.Width;

                    for (int row = 0; row < neghborhood; row++)
                    {
                        int subImageHeight = imgHeight - row;
                        for (int col = 0; col < neghborhood; col++)
                        {
                            OrImageBits(&srcImgPtr[col], destImgPtr,
                                        imgStride,
                                        imgWidth - col, subImageHeight);
                        }

                        srcImgPtr += imgStride;
                    }
                }

            return(destImg);
        }
        /// <summary>
        /// Computes gradient orientations from the color image. Orientation from the channel which has the maximum gradient magnitude is taken as the orientation for a location.
        /// </summary>
        /// <param name="frame">Image.</param>
        /// <param name="magnitudeSqrImage">Squared magnitude image.</param>
        /// <param name="minValidMagnitude">Minimal valid magnitude.</param>
        /// <returns>Orientation image (angles are in degrees).</returns>
        public unsafe static Gray <int>[,] Compute(Bgr <byte>[,] frame, out Gray <int>[,] magnitudeSqrImage, int minValidMagnitude)
        {
            var minSqrMagnitude = minValidMagnitude * minValidMagnitude;

            var orientationImage   = new Gray <int> [frame.Height(), frame.Width()];
            var _magnitudeSqrImage = orientationImage.CopyBlank();

            using (var uFrame = frame.Lock())
            {
                ParallelLauncher.Launch(thread =>
                {
                    computeColor(thread, (byte *)uFrame.ImageData, uFrame.Stride, orientationImage, _magnitudeSqrImage, minSqrMagnitude);
                },
                                        frame.Width() - 2 * kernelRadius, frame.Height() - 2 * kernelRadius);
            }

            magnitudeSqrImage = _magnitudeSqrImage;
            return(orientationImage);
        }
        /// <summary>
        /// Take only those orientations that have MINIMAL_NUM_OF_SAME_ORIENTED_PIXELS in 3x3 negborhood.
        /// Performs angle transformation into binary form ([0..7] -> [1, 2, 4, 8, ..., 128]) as well.
        /// </summary>
        /// <param name="qunatizedOrientionImg">Quantized orientation image where angles are represented by lables [0..GlobalParameters.NUM_OF_QUNATIZED_ORIENTATIONS] (invalid orientation label included).</param>
        /// <param name="minSameOrientations">Minimal number of same orientations for 3x3 neigborhood. The range is: [0..9] (3x3 neigborhood).</param>
        private static Gray <byte>[,] RetainImportantQuantizedOrientations(Gray <byte>[,] qunatizedOrientionImg, int minSameOrientations)
        {
            if (minSameOrientations < 0 || minSameOrientations > 9 /*3x3 neigborhood*/)
            {
                throw new Exception("Minimal number of same orientations should be in: [0..9].");
            }

            var quantizedFilteredOrient = qunatizedOrientionImg.CopyBlank();

            using (var uQunatizedOrientionImg = qunatizedOrientionImg.Lock())
                using (var uQuantizedFilteredOrient = quantizedFilteredOrient.Lock())
                {
                    //debugImg = new Image<Hsv, byte>(orientDegImg.Width, orientDegImg.Height);
                    //debugImg = null;
                    int qOrinetStride = uQunatizedOrientionImg.Stride;
                    int qOrinetAllign = uQunatizedOrientionImg.Stride - uQunatizedOrientionImg.Width;

                    byte *qOrinetUnfilteredPtr = (byte *)uQunatizedOrientionImg.ImageData + qOrinetStride + 1;
                    byte *qOrinetFilteredPtr   = (byte *)uQuantizedFilteredOrient.ImageData + qOrinetStride + 1;

                    //Debug.Assert(qunatizedOrientionImg.Stride == quantizedFilteredOrient.Stride);

                    int imgWidth  = uQunatizedOrientionImg.Width;
                    int imgHeight = uQunatizedOrientionImg.Height;

                    for (int j = 1; j < imgHeight - 1; j++)
                    {
                        for (int i = 1; i < imgWidth - 1; i++)
                        {
                            if (*qOrinetUnfilteredPtr != INVALID_QUANTIZED_ORIENTATION)
                            {
                                byte[] histogram = new byte[INVALID_QUANTIZED_ORIENTATION + 1]; //gleda se susjedstvo 3x3

                                histogram[qOrinetUnfilteredPtr[-qOrinetStride - 1]]++; histogram[qOrinetUnfilteredPtr[-qOrinetStride + 0]]++; histogram[qOrinetUnfilteredPtr[-qOrinetStride + 1]]++;
                                histogram[qOrinetUnfilteredPtr[-1]]++; histogram[qOrinetUnfilteredPtr[0]]++; histogram[qOrinetUnfilteredPtr[+1]]++;
                                histogram[qOrinetUnfilteredPtr[+qOrinetStride - 1]]++; histogram[qOrinetUnfilteredPtr[+qOrinetStride + 0]]++; histogram[qOrinetUnfilteredPtr[+qOrinetStride + 1]]++;

                                int maxBinVotes = 0; byte quantizedAngle = 0;
                                // find the max direction in the 3x3 box
                                for (byte histBinIdx = 0; histBinIdx < GlobalParameters.NUM_OF_QUNATIZED_ORIENTATIONS /*discard invalid orientation*/; histBinIdx++)
                                {
                                    if (histogram[histBinIdx] > maxBinVotes)
                                    {
                                        maxBinVotes    = histogram[histBinIdx];
                                        quantizedAngle = histBinIdx;
                                    }
                                }

                                if (maxBinVotes >= minSameOrientations)
                                {
                                    *qOrinetFilteredPtr = (byte)(1 << quantizedAngle); //[1,2,4,8...128] (8 orientations)
                                }
                                //*qOrinetFilteredPtr = (byte)(1 << *qOrinetUnfilteredPtr); //[1,2,4,8...128] (8 orientations)
                                //debugImg[j, i] = new Hsv((*qOrinetFilteredPtr-1) * 35, 100, 100);
                            }

                            qOrinetUnfilteredPtr++;
                            qOrinetFilteredPtr++;
                        }

                        qOrinetUnfilteredPtr += 1 + qOrinetAllign + 1;
                        qOrinetFilteredPtr   += 1 + qOrinetAllign + 1; //preskoči zadnji piksel, poravnanje, i početni piksel
                    }
                }

            //magnitudeImg.Save("magnitude.bmp");
            //quantizedFilteredOrient.Save("quantizedImg.bmp");
            return(quantizedFilteredOrient);
        }