// #############################################################################################################################
        // ################################# FOUR DIFFERENT METHODS TO CALCULATE THE BACKGROUND NOISE PROFILE
        //
        // (1) MODAL METHOD
        // (2) LOWEST PERCENTILE FRAMES METHOD
        // (3) BIN-WISE LOWEST PERCENTILE CELLS METHOD
        // (4) FIRST N FRAMES
        // ##################

        /// <summary>
        /// (1) MODAL METHOD
        /// Assumes the passed matrix is a spectrogram. i.e. rows=frames, cols=freq bins.
        /// Returns the noise profile over freq bins. i.e. one noise value per freq bin.
        /// </summary>
        /// <param name="matrix">the spectrogram with origin top-left</param>
        /// <param name="sdCount">number of standard deviations</param>
        public static NoiseProfile CalculateModalNoiseProfile(double[,] matrix, double sdCount)
        {
            int colCount = matrix.GetLength(1);

            double[] noiseMode      = new double[colCount];
            double[] noiseSd        = new double[colCount];
            double[] noiseThreshold = new double[colCount];
            double[] minsOfBins     = new double[colCount];
            double[] maxsOfBins     = new double[colCount];
            for (int col = 0; col < colCount; col++)
            {
                double[]            freqBin  = MatrixTools.GetColumn(matrix, col);
                SNR.BackgroundNoise binNoise = SNR.CalculateModalBackgroundNoiseInSignal(freqBin, sdCount);
                noiseMode[col]      = binNoise.NoiseMode;
                noiseSd[col]        = binNoise.NoiseSd;
                noiseThreshold[col] = binNoise.NoiseThreshold;
                minsOfBins[col]     = binNoise.MinDb;
                maxsOfBins[col]     = binNoise.MaxDb;
            }

            var profile = new NoiseProfile()
            {
                NoiseMode       = noiseMode,
                NoiseSd         = noiseSd,
                NoiseThresholds = noiseThreshold,
                MinDb           = minsOfBins,
                MaxDb           = maxsOfBins,
            };

            return(profile);
        }
        } //Analysis()

        public static Tuple <List <Dictionary <string, double> >, double[]> DetectGratingEvents(double[,] matrix, int colStep, double intensityThreshold)
        {
            bool doNoiseremoval = true;
            int  minPeriod      = 2;  //both period values must be even numbers
            int  maxPeriod      = 20; //Note: 17.2 frames per second i.e. period=20 is just over 1s.
            int  numberOfCycles = 4;
            int  step           = 1;

            int rowCount         = matrix.GetLength(0);
            int colCount         = matrix.GetLength(1);
            int numberOfColSteps = colCount / colStep;

            var events2return = new List <Dictionary <string, double> >();

            double[] array2return = null;

            for (int b = 0; b < numberOfColSteps; b++)
            {
                int minCol = (b * colStep);
                int maxCol = minCol + colStep - 1;

                double[,] subMatrix = MatrixTools.Submatrix(matrix, 0, minCol, (rowCount - 1), maxCol);
                double[] amplitudeArray = MatrixTools.GetRowAverages(subMatrix);

                if (doNoiseremoval)
                {
                    double StandardDeviationCount = 0.1; // number of noise SDs to calculate noise threshold - determines severity of noise reduction
                    SNR.BackgroundNoise bgn       = SNR.SubtractBackgroundNoiseFromSignal(amplitudeArray, StandardDeviationCount);
                    amplitudeArray = bgn.NoiseReducedSignal;
                }

                //var events = CrossCorrelation.DetectBarsEventsBySegmentationAndXcorrelation(amplitudeArray, intensityThreshold);

                var      scores       = Gratings.ScanArrayForGratingPattern(amplitudeArray, minPeriod, maxPeriod, numberOfCycles, step);
                var      mergedOutput = Gratings.MergePeriodicScoreArrays(scores, minPeriod, maxPeriod);
                double[] intensity    = mergedOutput.Item1;
                double[] periodicity  = mergedOutput.Item2;
                var      events       = Gratings.ExtractPeriodicEvents(intensity, periodicity, intensityThreshold);

                foreach (Dictionary <string, double> item in events)
                {
                    item[key_MIN_FREQBIN] = minCol;
                    item[key_MAX_FREQBIN] = maxCol;
                    events2return.Add(item);
                }

                if (b == 3)
                {
                    array2return = amplitudeArray;         //returned for debugging purposes only
                }
            } //for loop over bands of columns

            return(Tuple.Create(events2return, array2return));
        }//end DetectGratingEvents()
        //public const string key_COUNT = "count";

        public static Tuple <double[, ], double[, ], double[, ], double[]> DetectBarsUsingXcorrelation(double[,] m, int rowStep, int rowWidth, int colStep, int colWidth,
                                                                                                       double intensityThreshold, int zeroBinCount)
        {
            bool doNoiseremoval = true;

            //intensityThreshold = 0.3;

            int rowCount         = m.GetLength(0);
            int colCount         = m.GetLength(1);
            int numberOfColSteps = colCount / colStep;
            int numberOfRowSteps = rowCount / rowStep;

            var intensityMatrix   = new double[numberOfRowSteps, numberOfColSteps];
            var periodicityMatrix = new double[numberOfRowSteps, numberOfColSteps];
            var hitsMatrix        = new double[rowCount, colCount];

            double[] array2return = null;

            for (int b = 0; b < numberOfColSteps; b++)
            {
                int minCol = b * colStep;
                int maxCol = minCol + colWidth - 1;

                double[,] subMatrix = MatrixTools.Submatrix(m, 0, minCol, rowCount - 1, maxCol);
                double[] amplitudeArray = MatrixTools.GetRowAverages(subMatrix);

                if (doNoiseremoval)
                {
                    double StandardDeviationCount = 0.1;  // number of noise SDs to calculate noise threshold - determines severity of noise reduction
                    SNR.BackgroundNoise bgn       = SNR.SubtractBackgroundNoiseFromSignal(amplitudeArray, StandardDeviationCount);
                    amplitudeArray = bgn.NoiseReducedSignal;
                }

                //double noiseThreshold = 0.005;
                //for (int i = 1; i < amplitudeArray.Length - 1; i++)
                //{
                //    if ((amplitudeArray[i - 1] < noiseThreshold) && (amplitudeArray[i + 1] < noiseThreshold)) amplitudeArray[i] = 0.0;
                //}
                //DataTools.writeBarGraph(amplitudeArray);
                if (b == 2)
                {
                    array2return = amplitudeArray; //returned for debugging purposes only
                }

                //ii: DETECT HARMONICS
                var      results     = AutoAndCrossCorrelation.DetectPeriodicityInLongArray(amplitudeArray, rowStep, rowWidth, zeroBinCount);
                double[] intensity   = results.Item1;    //an array of periodicity scores
                double[] periodicity = results.Item2;

                //transfer periodicity info to a matrices.
                for (int rs = 0; rs < numberOfRowSteps; rs++)
                {
                    intensityMatrix[rs, b]   = intensity[rs];
                    periodicityMatrix[rs, b] = periodicity[rs];

                    //mark up the hits matrix
                    //double relativePeriod = periodicity[rs] / rowWidth / 2;
                    if (intensity[rs] > intensityThreshold)
                    {
                        int minRow = rs * rowStep;
                        int maxRow = minRow + rowStep - 1;
                        for (int r = minRow; r < maxRow; r++)
                        {
                            for (int c = minCol; c < maxCol; c++)
                            {
                                //hitsMatrix[r, c] = relativePeriod;
                                hitsMatrix[r, c] = periodicity[rs];
                            }
                        }
                    } // if()
                }     // for loop over numberOfRowSteps
            }         // for loop over numberOfColSteps

            return(Tuple.Create(intensityMatrix, periodicityMatrix, hitsMatrix, array2return));
        }
        } //DetectBarsInTheRowsOfaMatrix()

        /// A METHOD TO DETECT HARMONICS IN THE ROWS of the passed portion of a sonogram.
        /// This method assume the matrix is derived from a spectrogram rotated so that the matrix rows are spectral columns of sonogram.
        /// Was first developed for crow calls.
        /// First looks for a decibel profile that matches the passed call duration and decibel loudness
        /// Then samples the centre portion for the correct harmonic period.
        /// </summary>
        /// <param name="m"></param>
        /// <param name="amplitudeThreshold"></param>
        /// <returns></returns>
        public static Tuple <double[], double[], double[]> DetectHarmonicsInSonogramMatrix(double[,] m, double dBThreshold, int callSpan)
        {
            int zeroBinCount = 3;  //to remove low freq content which dominates the spectrum
            int halfspan     = callSpan / 2;

            double[] dBArray = MatrixTools.GetRowAverages(m);
            dBArray = DataTools.filterMovingAverage(dBArray, 3);

            bool doNoiseRemoval = true;

            if (doNoiseRemoval)
            {
                double StandardDeviationCount = 0.1;  // number of noise SDs to calculate noise threshold - determines severity of noise reduction
                SNR.BackgroundNoise bgn       = SNR.SubtractBackgroundNoiseFromSignal(dBArray, StandardDeviationCount);
                dBArray = bgn.NoiseReducedSignal;
            }

            bool[] peaks = DataTools.GetPeaks(dBArray);

            int rowCount    = m.GetLength(0);
            int colCount    = m.GetLength(1);
            var intensity   = new double[rowCount];    //an array of period intensity
            var periodicity = new double[rowCount];    //an array of the periodicity values

            for (int r = halfspan; r < rowCount - halfspan; r++)
            {
                //APPLY A FILTER: must satisfy the following conditions for a call.
                if (!peaks[r])
                {
                    continue;
                }

                if (dBArray[r] < dBThreshold)
                {
                    continue;
                }

                double lowerDiff = dBArray[r] - dBArray[r - halfspan];
                double upperDiff = dBArray[r] - dBArray[r + halfspan];
                if (lowerDiff < dBThreshold || upperDiff < dBThreshold)
                {
                    continue;
                }

                double[] prevRow  = DataTools.DiffFromMean(MatrixTools.GetRow(m, r - 1));
                double[] thisRow  = DataTools.DiffFromMean(MatrixTools.GetRow(m, r));
                var      spectrum = AutoAndCrossCorrelation.CrossCorr(prevRow, thisRow);

                for (int s = 0; s < zeroBinCount; s++)
                {
                    spectrum[s] = 0.0;  //in real data these bins are dominant and hide other frequency content
                }

                spectrum = DataTools.NormaliseArea(spectrum);
                int    maxId          = DataTools.GetMaxIndex(spectrum);
                double intensityValue = spectrum[maxId];
                intensity[r] = intensityValue;

                double period = 0.0;
                if (maxId != 0)
                {
                    period = 2 * colCount / (double)maxId;
                }

                periodicity[r] = period;

                prevRow = thisRow;
            } // rows

            return(Tuple.Create(dBArray, intensity, periodicity));
        } //DetectHarmonicsInSonogramMatrix()