// #############################################################################################################################
        // ################################# 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);
        }
        /// <summary>
        /// (3) BIN-WISE LOWEST PERCENTILE CELLS METHOD
        /// Assumes the passed matrix is a spectrogram.
        /// Returns the noise profile over freq bins. i.e. one noise value per freq bin.
        /// IMPORTANT: This is the preferred method to estiamte a noise profile for short recordings i.e LT approx 10-15 seconds long.
        /// </summary>
        /// <param name="matrix">the spectrogram whose rows=frames, cols=freq bins.</param>
        /// <param name="lowPercentile">The percent of lowest energy frames to be included in calculation of the noise profile.</param>
        public static double[] GetNoiseProfile_BinWiseFromLowestPercentileCells(double[,] matrix, int lowPercentile)
        {
            int rowCount = matrix.GetLength(0);
            int colCount = matrix.GetLength(1);
            int cutoff   = lowPercentile * rowCount / 100;

            if (cutoff == 0)
            {
                throw new Exception("Illegal zero value for cutoff in method NoiseRemoval_Briggs.GetNoiseProfile_LowestPercentile()");
            }

            double[] noiseProfile = new double[colCount];

            // loop over all frequency bins
            for (int bin = 0; bin < colCount; bin++)
            {
                double[] freqBin      = MatrixTools.GetColumn(matrix, bin);
                double[] orderedArray = (double[])freqBin.Clone();
                Array.Sort(orderedArray);
                double sum = 0.0;
                for (int i = 0; i < cutoff; i++)
                {
                    sum += orderedArray[i];
                }

                noiseProfile[bin] = sum / cutoff;
            }

            return(noiseProfile);
        }
Esempio n. 3
0
        /// <summary>
        /// Assumes the passed matrix is a spectrogram. i.e. rows=frames, cols=freq bins.
        /// First obtains background noise profile calculated from lowest 20% of cells for each freq bin independently.
        /// Loop over freq bins (columns) - subtract noise and divide by LCN (Local Contrast Normalisation.
        ///
        /// The LCN denominator = (contrastLevelConstant + Sqrt(localVariance[y])
        /// Note that sqrt of variance = std dev.
        /// A low contrastLevel = 0.1 give more grey image.
        /// A high contrastLevel = 1.0 give mostly white high contrast image.
        /// </summary>
        public static double[,] NoiseReduction_ShortRecordings_SubtractAndLCN(double[,] matrix, int lowPercent, int neighbourhood, double contrastLevel)
        {
            double[] noiseProfile = NoiseProfile.GetNoiseProfile_BinWiseFromLowestPercentileCells(matrix, lowPercent);
            noiseProfile = DataTools.filterMovingAverage(noiseProfile, 5);
            int rowCount = matrix.GetLength(0);
            int colCount = matrix.GetLength(1);

            //to contain noise reduced matrix
            double[,] outM = new double[rowCount, colCount];

            //for all cols i.e. freq bins
            for (int col = 0; col < colCount; col++)
            {
                double[] column        = MatrixTools.GetColumn(matrix, col);
                double[] localVariance = NormalDist.CalculateLocalVariance(column, neighbourhood);

                // NormaliseMatrixValues with local column variance
                // for all rows
                for (int y = 0; y < rowCount; y++)
                {
                    //outM[y, col] = matrix[y, col] / (contrastLevel + localVariance[y]);
                    outM[y, col] = (matrix[y, col] - noiseProfile[col]) / (contrastLevel + Math.Sqrt(localVariance[y]));
                } //end for all rows
            }     //end for all cols

            return(outM);
        }
Esempio n. 4
0
        /// <summary>
        /// Assumes the passed matrix is a spectrogram. i.e. rows=frames, cols=freq bins.
        /// Does column-wise LCN (Local Contrast Normalisation.
        /// The denominator = (contrastLevel + Math.Sqrt(localVariance[y])
        /// A low contrastLevel = 0.1 give more grey image.
        /// A high contrastLevel = 1.0 give mostly white high contrast image.
        /// It tried various other normalisation equations as can be seen below.
        /// Taking square-root of top line results in too much background.
        /// The algorithm is not overly sensitive to the neighbourhood size.
        /// </summary>
        /// <param name="neighbourhood">suitable vaues are odd numbers 9 - 59.</param>
        /// <param name="contrastLevel">Suitable values are 0.1 to 1.0.</param>
        public static double[,] NoiseReduction_byLCNDivision(double[,] matrix, int neighbourhood, double contrastLevel)
        {
            int rowCount = matrix.GetLength(0);
            int colCount = matrix.GetLength(1);

            //to contain noise reduced matrix
            double[,] outM = new double[rowCount, colCount];

            //for all cols i.e. freq bins
            for (int col = 0; col < colCount; col++)
            {
                double[] column        = MatrixTools.GetColumn(matrix, col);
                double[] localVariance = NormalDist.CalculateLocalVariance(column, neighbourhood);

                // NormaliseMatrixValues with local column variance
                // for all rows
                for (int y = 0; y < rowCount; y++)
                {
                    //outM[y, col] = matrix[y, col] / (contrastLevel + localVariance[y]);
                    outM[y, col] = matrix[y, col] / (contrastLevel + Math.Sqrt(localVariance[y]));

                    //outM[y, col] = Math.Sqrt(matrix[y, col]) / (contrastLevel + Math.Sqrt(localVariance[y]));
                    //outM[y, col] = Math.Sqrt(matrix[y, col] / (contrastLevel + Math.Sqrt(localVariance[y])));
                    //outM[y, col] = matrix[y, col] / (1 + (1.0 * Math.Sqrt(localVariance[y])));
                    //outM[y, col] = Math.Sqrt(matrix[y, col] / (1 + (0.10 * localVariance[y])));
                } //end for all rows
            }     //end for all cols

            return(outM);
        }
        /// <summary>
        /// (1) MEAN SUBTRACTION
        /// 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.
        /// Note that NoiseThresholds array is identical to NoiseMedian array.
        /// </summary>
        /// <param name="matrix">the spectrogram with origin top-left</param>
        public static NoiseProfile CalculateMeanNoiseProfile(double[,] matrix)
        {
            int colCount = matrix.GetLength(1);

            double[] noiseMean  = 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);
                noiseMean[col]  = freqBin.Average();
                minsOfBins[col] = freqBin.Min();
                maxsOfBins[col] = freqBin.Max();
            }

            var profile = new NoiseProfile()
            {
                NoiseMean       = noiseMean,
                NoiseSd         = null,
                NoiseThresholds = noiseMean,
                MinDb           = minsOfBins,
                MaxDb           = maxsOfBins,
            };

            return(profile);
        }
        public static NoiseProfile CalculatePercentileNoiseProfile(double[,] matrix, int percentile)
        {
            int rowCount = matrix.GetLength(0);
            int colCount = matrix.GetLength(1);

            double[] noiseMedian = 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);
                Array.Sort(freqBin);
                noiseMedian[col] = freqBin[rowCount * percentile / 100];
                minsOfBins[col]  = freqBin.Min();
                maxsOfBins[col]  = freqBin.Max();
            }

            var profile = new NoiseProfile()
            {
                NoiseMedian     = noiseMedian,
                NoiseSd         = null,
                NoiseThresholds = noiseMedian,
                MinDb           = minsOfBins,
                MaxDb           = maxsOfBins,
            };

            return(profile);
        }
Esempio n. 7
0
        public void CompressIndexSpectrogramsFillsAllValuesTest(double renderScale, int dataSize, string key)
        {
            var someSpectra = new double[256, dataSize].Fill(-100);
            var spectra     = new Dictionary <string, double[, ]> {
                { key, someSpectra },
            };
            var compressed = IndexMatrices.CompressIndexSpectrograms(
                spectra,
                renderScale.Seconds(),
                0.1.Seconds(),
                d => Math.Round(d, MidpointRounding.AwayFromZero));

            // ReSharper disable once CompareOfFloatsByEqualityOperator (We are testing exact values)
            if (renderScale == 0.1)
            {
                // in cases where the scales are equal, the method should short circuit and
                // just return the same matrix.
                Assert.AreEqual(spectra, compressed);
            }

            var compressedSpectra = compressed[key];
            var average           = compressedSpectra.Average();

            // this test is specifically testing whether the last column has the correct value
            var lastColumn        = MatrixTools.GetColumn(compressedSpectra, compressedSpectra.LastColumnIndex());
            var lastColumnAverage = lastColumn.Average();

            Assert.AreEqual(-100, lastColumnAverage, 0.0000000001, $"Expected last column to have value -100 but it was {lastColumnAverage:R}");

            Assert.AreEqual(-100, average, 0.0000000001, $"Expected total average to be -100 but it was {average:R}");
        }
Esempio n. 8
0
        /// <summary>
        /// returns an oscillation array for a single frequency bin.
        /// </summary>
        /// <param name="xCorrByTimeMatrix">derived from single frequency bin.</param>
        /// <param name="sensitivity">a threshold used to ignore low ascillation intensities.</param>
        /// <returns>vector of oscillation values.</returns>
        public static double[] GetOscillationArrayUsingFft(double[,] xCorrByTimeMatrix, double sensitivity)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // set up vector to contain fft output
            var oscillationsVector = new double[xCorrLength / 2];

            // loop over all the Auto-correlation vectors and do FFT
            for (int e = 0; e < sampleCount; e++)
            {
                double[] autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);

                // zero mean the auto-correlation vector before doing FFT
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in the low coeff because these can dominate.
                // This is a hack!
                spectrum[0] *= 0.33;

                // convert to energy and calculate total power in spectrum
                spectrum = DataTools.SquareValues(spectrum);
                double sumOfSquares = spectrum.Sum();

                // get combined relative power in the three bins centred on max.
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];

                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[1] + spectrum[2];
                }
                else if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1] + spectrum[maxIndex + 1];
                }

                double relativePower = powerAtMax / sumOfSquares;

                // if the relative power of the max oscillation is large enough,
                // then accumulate its power into the oscillations Vector
                if (relativePower > sensitivity)
                {
                    oscillationsVector[maxIndex] += powerAtMax;
                }
            }

            return(LogTransformOscillationVector(oscillationsVector, sampleCount));
        }
Esempio n. 9
0
        public static double[] GetOscillationArrayUsingWpd(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            double[] oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < sampleCount; e++)
            {
                var autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);
                autocor = DataTools.DiffFromMean(autocor);
                var      wpd      = new WaveletPacketDecomposition(autocor);
                double[] spectrum = wpd.GetWPDEnergySpectrumWithoutDC();

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.5;

                spectrum = DataTools.SquareValues(spectrum);

                // get relative power in the three bins around max.
                double sumOfSquares = spectrum.Sum();

                //double avPower = spectrum.Sum() / spectrum.Length;
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];
                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1];
                }

                if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex + 1];
                }

                double relativePower1 = powerAtMax / sumOfSquares;

                if (relativePower1 > sensitivity)
                {
                    // check for boundary overrun
                    if (maxIndex < oscillationsVector.Length)
                    {
                        // add in a new oscillation
                        oscillationsVector[maxIndex] += powerAtMax;
                    }
                }
            }

            return(LogTransformOscillationVector(oscillationsVector, sampleCount));
        }
Esempio n. 10
0
        public void CropTrack(BaseSonogram sonogram, double threshold)
        {
            //int length = sonogram.FrameCount;
            int binID = (int)this.AverageBin;

            double[] freqBin = MatrixTools.GetColumn(sonogram.Data, binID);

            double[] subArray = DataTools.Subarray(freqBin, this.StartFrame, this.Length);
            int[]    bounds   = DataTools.Peaks_CropLowAmplitude(subArray, threshold);

            this.EndFrame    = this.StartFrame + bounds[1];
            this.StartFrame += bounds[0];
        }
        //############################################################################################################################################################
        //# BELOW METHODS CALCULATE SUMMARY INDEX RIBBONS ############################################################################################################
        //# NOTE As of 2018, summary index ribbons are no longer produced. An idea that did not work!
        //# WARNING: THE BELOW METHODS WILL PROBABLY NOT WORK DUE TO SUBSEQUENT REFACTORING OF LDSpectrogramRGB class.

        /// <summary>
        /// Returns an array of summary indices, where each element of the array (one element per minute) is a single summary index
        /// derived by averaging the spectral indices for that minute.
        /// The returned matrices have spectrogram orientation.
        /// </summary>
        public static double[] GetSummaryIndexArray(double[,] m)
        {
            int colcount = m.GetLength(1);

            double[] indices = new double[colcount];

            for (int r = 0; r < colcount; r++)
            {
                indices[r] = MatrixTools.GetColumn(m, r).Average();
            }

            return(indices);
        }
        /// <summary>
        /// Use this method to average a decibel spectrogram.
        /// </summary>
        public static double[] CalculateAvgDecibelSpectrumFromDecibelSpectrogram(double[,] spectrogram)
        {
            int freqBinCount = spectrogram.GetLength(1);

            double[] avgSpectrum = new double[freqBinCount];
            for (int j = 0; j < freqBinCount; j++)
            {
                var    freqBin = MatrixTools.GetColumn(spectrogram, j);
                double av      = AverageAnArrayOfDecibelValues(freqBin);
                avgSpectrum[j] = av;
            }

            return(avgSpectrum);
        }
Esempio n. 13
0
        }//end AI_DimRed

        public static List <double[]> Sonogram2ListOfFreqBinArrays(BaseSonogram sonogram, double dynamicRange)
        {
            //int rowCount = sonogram.Data.GetLength(0);
            int colCount = sonogram.Data.GetLength(1);

            //set up a list of normalised arrays representing the spectrum - one array per freq bin
            var listOfFrequencyBins = new List <double[]>();

            for (int c = 0; c < colCount; c++)
            {
                double[] array = MatrixTools.GetColumn(sonogram.Data, c);
                array = DataTools.NormaliseInZeroOne(array, 0, 50); //##IMPORTANT: ABSOLUTE NORMALISATION 0-50 dB #######################################
                listOfFrequencyBins.Add(array);
            }

            return(listOfFrequencyBins);
        } // Sonogram2ListOfFreqBinArrays()
        public static double[] CalculateTemporalEntropySpectrum(double[,] spectrogram)
        {
            // int frameCount = spectrogram.GetLength(0);
            int freqBinCount = spectrogram.GetLength(1);

            double[] tenSp = new double[freqBinCount];      // array of H[t] indices, one for each freq bin

            // for all frequency bins
            for (int j = 0; j < freqBinCount; j++)
            {
                double[] column = MatrixTools.GetColumn(spectrogram, j);

                // ENTROPY of freq bin
                tenSp[j] = DataTools.EntropyNormalised(DataTools.SquareValues(column));
            }

            return(tenSp);
        }
        /// <summary>
        /// Take average of the energy values in each frequency bin to obtain power spectrum or PSD.
        /// SpectrogramTools.CalculateAvgSpectrumFromEnergySpectrogram is doing the same!
        /// </summary>
        public static double[] GetPowerSpectrum(double[,] energySpectrogram)
        {
            double[] powerSpectrum = new double[energySpectrogram.GetLength(1)];
            for (int j = 0; j < energySpectrogram.GetLength(1); j++)
            {
                /*
                 * double sum = 0;
                 * for (int i = 0; i < energySpectrogram.GetLength(0); i++)
                 * {
                 *  sum += energySpectrogram[i, j];
                 * }
                 * powerSpectrum[j] = sum / energySpectrogram.GetLength(0);
                 */

                powerSpectrum[j] = MatrixTools.GetColumn(energySpectrogram, j).Average();
            }

            return(powerSpectrum);
        }
Esempio n. 16
0
        public void CompressIndexSpectrogramsFillsAllValuesTest(double renderScale, int dataSize)
        {
            var bgnSpectra = new double[256, dataSize].Fill(-100);
            var spectra    = new Dictionary <string, double[, ]> {
                { "BGN", bgnSpectra },
            };
            var compressed = IndexMatrices.CompressIndexSpectrograms(
                spectra,
                renderScale.Seconds(),
                0.1.Seconds(),
                d => Math.Round(d, MidpointRounding.AwayFromZero));

            var bgn     = compressed["BGN"];
            var average = bgn.Average();

            // this test is specifically testing whether the last column has the correct value
            var lastColumn = MatrixTools.GetColumn(bgn, bgn.LastColumnIndex());

            Assert.AreEqual(-100, lastColumn.Average());

            Assert.AreEqual(-100, average);
        }
Esempio n. 17
0
        /// <summary>
        /// Returns the number of acoustic events per second in the each frequency bin.
        /// Also returns the fractional cover in each freq bin, that is, the fraction of frames where amplitude > threshold.
        /// WARNING NOTE: If you call this method, you must provide the low and mid-freq bounds as BIN IDs, NOT as Herz values.
        /// </summary>
        public static SpectralActivity CalculateSpectralEvents(double[,] spectrogram, double dbThreshold, TimeSpan frameStepDuration, int lowFreqBinIndex, int midFreqBinIndex)
        {
            int    rows = spectrogram.GetLength(0); // frames
            int    cols = spectrogram.GetLength(1); // # of freq bins
            double recordingDurationInSeconds = rows * frameStepDuration.TotalSeconds;

            double[] coverSpectrum = new double[cols];
            double[] eventSpectrum = new double[cols];

            // for each frequency bin, calculate coverage
            for (int c = 0; c < cols; c++)
            {
                // get the freq bin containing dB values
                double[] bin = MatrixTools.GetColumn(spectrogram, c);

                // get activity and event info
                var activity = CalculateActivity(bin, frameStepDuration, dbThreshold);

                //bool[] a1 = activity.activeFrames;
                //int a2 = activity.activeFrameCount;
                coverSpectrum[c] = activity.FractionOfActiveFrames;

                //double a4 = activity.activeAvDB;
                eventSpectrum[c] = activity.EventCount / recordingDurationInSeconds;

                //TimeSpan a6 = activity.avEventDuration;
                //bool[] a7 = activity.eventLocations;
            }

            // calculate coverage for low freq band as a percentage
            int    count = 0;
            double sum   = 0;

            for (int j = 0; j < lowFreqBinIndex; j++)
            {
                sum += coverSpectrum[j];
                count++;
            }

            double lowFreqCover = sum / count;

            // calculate coverage for mid freq band as a percentage
            count = 0;
            sum   = 0;
            for (int j = lowFreqBinIndex; j < midFreqBinIndex; j++)
            {
                sum += coverSpectrum[j];
                count++;
            }

            double midFreqCover = sum / count;

            // calculate coverage for high freq band as a percentage
            // avoid top row which can have edge effects
            int highFreqBinIndex = spectrogram.GetLength(1) - 1;

            count = 0;
            sum   = 0;
            for (int j = midFreqBinIndex; j < highFreqBinIndex; j++)
            {
                sum += coverSpectrum[j];
                count++;
            }

            double highFreqCover = sum / count;

            return(new SpectralActivity(eventSpectrum, coverSpectrum, lowFreqCover, midFreqCover, highFreqCover));
        }
        /// <summary>
        /// returns a Long Duration spectrogram of same image length as the full-scale LdSpectrogram but the frequency scale reduced to the passed vlaue of height.
        /// This produces a LD spectrogram "ribbon" which can be used in circumstances where the full image is not appropriate.
        /// Note that if the height passed is a power of 2, then the full frequency scale (also a power of 2 due to FFT) can be scaled down exactly.
        /// A height of 32 is quite good - small but still discriminates frequency bands.
        /// </summary>
        public static Image <Rgb24> GetSpectrogramRibbon(double[,] indices1, double[,] indices2, double[,] indices3)
        {
            int height = RibbonPlotHeight;
            int width  = indices1.GetLength(1);
            var image  = new Image <Rgb24>(width, height);

            // get the reduced spectra of indices in each minute.
            // calculate the reduction factor i.e. freq bins per pixel row
            int bandWidth = indices1.GetLength(0) / height;

            for (int i = 0; i < width; i++)
            {
                var spectrum1 = MatrixTools.GetColumn(indices1, i);
                var spectrum2 = MatrixTools.GetColumn(indices2, i);
                var spectrum3 = MatrixTools.GetColumn(indices3, i);
                for (int h = 0; h < height; h++)
                {
                    int      start    = h * bandWidth;
                    double[] subArray = DataTools.Subarray(spectrum1, start, bandWidth);

                    // reduce full spectrum to ribbon by taking the AVERAGE of sub-bands.
                    // If the resulting value is NaN, then set the colour to grey by setting index to 0.5.
                    double index = subArray.Average();
                    if (double.IsNaN(index))
                    {
                        index = 0.5;
                    }

                    int red = (int)(255 * index);
                    if (red > 255)
                    {
                        red = 255;
                    }

                    subArray = DataTools.Subarray(spectrum2, start, bandWidth);
                    index    = subArray.Average();
                    if (double.IsNaN(index))
                    {
                        index = 0.5;
                    }

                    int grn = (int)(255 * index);
                    if (grn > 255)
                    {
                        grn = 255;
                    }

                    subArray = DataTools.Subarray(spectrum3, start, bandWidth);
                    index    = subArray.Average();
                    if (double.IsNaN(index))
                    {
                        index = 0.5;
                    }

                    int blu = (int)(255 * index);
                    if (blu > 255)
                    {
                        blu = 255;
                    }

                    image[i, h] = Color.FromRgb((byte)red, (byte)grn, (byte)blu);
                }
            }

            return(image);
        }
Esempio n. 19
0
        /// <summary>
        /// ################ THE KEY ANALYSIS METHOD.
        /// </summary>
        public static Tuple <BaseSonogram, double[, ], List <Plot>, List <AcousticEvent>, TimeSpan> Analysis(FileInfo fiSegmentOfSourceFile, Dictionary <string, string> configDict, TimeSpan segmentStartOffset)
        {
            //set default values - ignore those set by user
            int    frameSize     = 128;
            double windowOverlap = 0.5;

            double intensityThreshold = double.Parse(configDict["INTENSITY_THRESHOLD"]); //in 0-1
            double minDuration        = double.Parse(configDict["MIN_DURATION"]);        // seconds
            double maxDuration        = double.Parse(configDict["MAX_DURATION"]);        // seconds
            double minPeriod          = double.Parse(configDict["MIN_PERIOD"]);          // seconds
            double maxPeriod          = double.Parse(configDict["MAX_PERIOD"]);          // seconds

            AudioRecording recording = new AudioRecording(fiSegmentOfSourceFile.FullName);

            //i: MAKE SONOGRAM
            SonogramConfig sonoConfig = new SonogramConfig
            {
                SourceFName        = recording.BaseName,
                WindowSize         = frameSize,
                WindowOverlap      = windowOverlap,
                NoiseReductionType = SNR.KeyToNoiseReductionType("STANDARD"),
            }; //default values config

            //sonoConfig.NoiseReductionType = SNR.Key2NoiseReductionType("NONE");
            TimeSpan tsRecordingtDuration = recording.Duration;
            int      sr              = recording.SampleRate;
            double   freqBinWidth    = sr / (double)sonoConfig.WindowSize;
            double   frameOffset     = sonoConfig.GetFrameOffset(sr);
            double   framesPerSecond = 1 / frameOffset;

            BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
            int          rowCount = sonogram.Data.GetLength(0);
            int          colCount = sonogram.Data.GetLength(1);

            //#############################################################################################################################################
            //window    sr          frameDuration   frames/sec  hz/bin  64frameDuration hz/64bins       hz/128bins
            // 1024     22050       46.4ms          21.5        21.5    2944ms          1376hz          2752hz
            // 256      17640       14.5ms          68.9        68.9    ms          hz          hz
            // 512      17640       29.0ms          34.4        34.4    ms          hz          hz
            // 1024     17640       58.0ms          17.2        17.2    3715ms          1100hz          2200hz
            // 2048     17640       116.1ms          8.6         8.6    7430ms           551hz          1100hz

            //The Xcorrelation-FFT technique requires number of bins to scan to be power of 2.
            // Assuming sr=17640 and window=256, then binWidth = 68.9Hz and 1500Hz = bin 21.7..
            // Therefore do a Xcorrelation between bins 21 and 22.
            // Number of frames to span must power of 2. Try 16 frames which covers 232ms - almost 1/4 second.

            int midHz    = 1500;
            int lowerBin = (int)(midHz / freqBinWidth) + 1;  //because bin[0] = DC
            int upperBin = lowerBin + 4;
            int lowerHz  = (int)Math.Floor((lowerBin - 1) * freqBinWidth);
            int upperHz  = (int)Math.Ceiling((upperBin - 1) * freqBinWidth);

            //ALTERNATIVE IS TO USE THE AMPLITUDE SPECTRUM
            //var results2 = DSP_Frames.ExtractEnvelopeAndFFTs(recording.GetWavReader().Samples, sr, frameSize, windowOverlap);
            //double[,] matrix = results2.Item3;  //amplitude spectrogram. Note that column zero is the DC or average energy value and can be ignored.
            //double[] avAbsolute = results2.Item1; //average absolute value over the minute recording
            ////double[] envelope = results2.Item2;
            //double windowPower = results2.Item4;

            double[] lowerArray = MatrixTools.GetColumn(sonogram.Data, lowerBin);
            double[] upperArray = MatrixTools.GetColumn(sonogram.Data, upperBin);
            lowerArray = DataTools.NormaliseInZeroOne(lowerArray, 0, 60); //## ABSOLUTE NORMALISATION 0-60 dB #######################################################################
            upperArray = DataTools.NormaliseInZeroOne(upperArray, 0, 60); //## ABSOLUTE NORMALISATION 0-60 dB #######################################################################

            int step         = (int)(framesPerSecond / 40);               //take one/tenth second steps
            int stepCount    = rowCount / step;
            int sampleLength = 32;                                        //16 frames = 232ms - almost 1/4 second.

            double[] intensity   = new double[rowCount];
            double[] periodicity = new double[rowCount];

            //######################################################################
            //ii: DO THE ANALYSIS AND RECOVER SCORES

            for (int i = 0; i < stepCount; i++)
            {
                int      start         = step * i;
                double[] lowerSubarray = DataTools.Subarray(lowerArray, start, sampleLength);
                double[] upperSubarray = DataTools.Subarray(upperArray, start, sampleLength);
                if (lowerSubarray == null || upperSubarray == null)
                {
                    break;
                }

                if (lowerSubarray.Length != sampleLength || upperSubarray.Length != sampleLength)
                {
                    break;
                }

                var spectrum  = AutoAndCrossCorrelation.CrossCorr(lowerSubarray, upperSubarray);
                int zeroCount = 2;
                for (int s = 0; s < zeroCount; s++)
                {
                    spectrum[s] = 0.0;  //in real data these bins are dominant and hide other frequency content
                }

                int    maxId  = DataTools.GetMaxIndex(spectrum);
                double period = 2 * sampleLength / (double)maxId / framesPerSecond; //convert maxID to period in seconds
                if (period < minPeriod || period > maxPeriod)
                {
                    continue;
                }

                // lay down score for sample length
                for (int j = 0; j < sampleLength; j++)
                {
                    if (intensity[start + j] < spectrum[maxId])
                    {
                        intensity[start + j] = spectrum[maxId];
                    }

                    periodicity[start + j] = period;
                }
            }

            //iii: CONVERT SCORES TO ACOUSTIC EVENTS
            intensity = DataTools.filterMovingAverage(intensity, 3);
            intensity = DataTools.NormaliseInZeroOne(intensity, 0, 0.5); //## ABSOLUTE NORMALISATION 0-0.5 #######################################################################

            List <AcousticEvent> predictedEvents = AcousticEvent.ConvertScoreArray2Events(
                intensity,
                lowerHz,
                upperHz,
                sonogram.FramesPerSecond,
                freqBinWidth,
                intensityThreshold,
                minDuration,
                maxDuration,
                segmentStartOffset);

            CropEvents(predictedEvents, upperArray, segmentStartOffset);
            var hits = new double[rowCount, colCount];

            var plots = new List <Plot>();

            //plots.Add(new Plot("lowerArray", DataTools.Normalise(lowerArray, 0, 100), 10.0));
            //plots.Add(new Plot("lowerArray", DataTools.Normalise(lowerArray, 0, 100), 10.0));
            //plots.Add(new Plot("lowerArray", DataTools.NormaliseMatrixValues(lowerArray), 0.25));
            //plots.Add(new Plot("upperArray", DataTools.NormaliseMatrixValues(upperArray), 0.25));
            //plots.Add(new Plot("intensity",  DataTools.NormaliseMatrixValues(intensity), intensityThreshold));
            plots.Add(new Plot("intensity", intensity, intensityThreshold));

            return(Tuple.Create(sonogram, hits, plots, predictedEvents, tsRecordingtDuration));
        } //Analysis()
        public static double[] GetOscillationArrayUsingFft(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            // loop over the Auto-correlation vectors and do FFT
            double[] oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < sampleCount; e++)
            {
                double[] autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);

                //DataTools.writeBarGraph(autocor);

                // ##########################################################\
                autocor = DataTools.DiffFromMean(autocor);
                FFT.WindowFunc wf       = FFT.Hamming;
                var            fft      = new FFT(autocor.Length, wf);
                var            spectrum = fft.Invoke(autocor);

                // skip spectrum[0] because it is DC or zero oscillations/sec
                spectrum = DataTools.Subarray(spectrum, 1, spectrum.Length - 2);

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.66;

                spectrum = DataTools.SquareValues(spectrum);

                // get relative power in the three bins around max.
                double sumOfSquares = spectrum.Sum();

                //double avPower = spectrum.Sum() / spectrum.Length;
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];
                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1];
                }

                if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex + 1];
                }

                double relativePower1 = powerAtMax / sumOfSquares;

                //double relativePower2 = powerAtMax / avPower;

                if (relativePower1 > sensitivity)

                //if (relativePower2 > 10.0)
                {
                    // check for boundary overrun
                    if (maxIndex < oscillationsVector.Length)
                    {
                        // add in a new oscillation
                        oscillationsVector[maxIndex] += powerAtMax;

                        //oscillationsVector[maxIndex] += relativePower;
                    }
                }
            }

            for (int i = 0; i < oscillationsVector.Length; i++)
            {
                // NormaliseMatrixValues by sample count
                oscillationsVector[i] /= sampleCount;

                // do log transform
                if (oscillationsVector[i] < 1.0)
                {
                    oscillationsVector[i] = 0.0;
                }
                else
                {
                    oscillationsVector[i] = Math.Log10(1 + oscillationsVector[i]);
                }
            }

            return(oscillationsVector);
        }
        public static double[] GetOscillationArrayUsingWpd(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber)
        {
            int xCorrLength = xCorrByTimeMatrix.GetLength(0);
            int sampleCount = xCorrByTimeMatrix.GetLength(1);

            double[] oscillationsVector = new double[xCorrLength / 2];

            for (int e = 0; e < sampleCount; e++)
            {
                double[] autocor = MatrixTools.GetColumn(xCorrByTimeMatrix, e);

                // ##########################################################\
                autocor = DataTools.DiffFromMean(autocor);
                WaveletPacketDecomposition wpd = new WaveletPacketDecomposition(autocor);
                double[] spectrum = wpd.GetWPDEnergySpectrumWithoutDC();

                // reduce the power in first coeff because it can dominate - this is a hack!
                spectrum[0] *= 0.66;

                spectrum = DataTools.SquareValues(spectrum);

                // get relative power in the three bins around max.
                double sumOfSquares = spectrum.Sum();

                //double avPower = spectrum.Sum() / spectrum.Length;
                int    maxIndex   = DataTools.GetMaxIndex(spectrum);
                double powerAtMax = spectrum[maxIndex];
                if (maxIndex == 0)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex - 1];
                }

                if (maxIndex >= spectrum.Length - 1)
                {
                    powerAtMax += spectrum[maxIndex];
                }
                else
                {
                    powerAtMax += spectrum[maxIndex + 1];
                }

                double relativePower1 = powerAtMax / sumOfSquares;

                //double relativePower2 = powerAtMax / avPower;

                if (relativePower1 > sensitivity)

                //if (relativePower2 > 10.0)
                {
                    // check for boundary overrun
                    if (maxIndex < oscillationsVector.Length)
                    {
                        // add in a new oscillation
                        oscillationsVector[maxIndex] += powerAtMax;

                        //oscillationsVector[maxIndex] += relativePower;
                    }
                }
            }

            for (int i = 0; i < oscillationsVector.Length; i++)
            {
                // NormaliseMatrixValues by sample count
                oscillationsVector[i] /= sampleCount;

                // do log transform
                if (oscillationsVector[i] < 1.0)
                {
                    oscillationsVector[i] = 0.0;
                }
                else
                {
                    oscillationsVector[i] = Math.Log10(1 + oscillationsVector[i]);
                }
            }

            return(oscillationsVector);
        }
        public void GetRidgeSpectraVersion1(double[,] dbSpectrogramData, double ridgeThreshold)
        {
            int rowCount  = dbSpectrogramData.GetLength(0);
            int colCount  = dbSpectrogramData.GetLength(1);
            int spanCount = rowCount - 4; // 4 because 5x5 grid means buffer of 2 on either side

            double[,] matrix = dbSpectrogramData;

            //double[,] matrix = ImageTools.WienerFilter(dbSpectrogramData, 3);
            // returns a byte matrix of ridge directions
            // 0 = no ridge detected or below magnitude threshold.
            // 1 = ridge direction = horizontal or slope = 0;
            // 2 = ridge is positive slope or pi/4
            // 3 = ridge is vertical or pi/2
            // 4 = ridge is negative slope or 3pi/4.
            //byte[,] hits = RidgeDetection.Sobel5X5RidgeDetectionExperiment(matrix, ridgeThreshold);
            byte[,] hits = RidgeDetection.Sobel5X5RidgeDetectionVersion1(matrix, ridgeThreshold);

            //image for debugging
            //ImageTools.DrawMatrix(hits, @"C:\SensorNetworks\Output\BIRD50\temp\hitsSpectrogram.png");

            double[] spectrum = new double[colCount];
            byte[]   freqBin;

            //Now aggregate hits to get ridge info
            //note that the Spectrograms were passed in flat-rotated orientation.
            //Therefore need to assign ridge number to re-oriented values.
            // Accumulate info for the horizontal ridges
            for (int col = 0; col < colCount; col++)
            {
                // i.e. for each frequency bin
                freqBin = MatrixTools.GetColumn(hits, col);
                int count = freqBin.Count(x => x == 3);
                if (count < 2)
                {
                    continue; // i.e. not a track.
                }

                spectrum[col] = count / (double)spanCount;
            }

            this.RhzSpectrum = spectrum;

            // accumulate info for the vertical ridges
            spectrum = new double[colCount];
            for (int col = 0; col < colCount; col++)
            {
                // i.e. for each frequency bin
                freqBin = MatrixTools.GetColumn(hits, col);
                int count = freqBin.Count(x => x == 1);
                if (count < 2)
                {
                    continue; // i.e. not a track.
                }

                spectrum[col] = count / (double)spanCount;
            }

            this.RvtSpectrum = spectrum;

            // accumulate info for the up slope ridges
            spectrum = new double[colCount];
            for (int col = 0; col < colCount; col++)
            {
                // i.e. for each frequency bin
                freqBin = MatrixTools.GetColumn(hits, col);
                int count = freqBin.Count(x => x == 4);
                spectrum[col] = count / (double)spanCount;
            }

            this.RpsSpectrum = spectrum;

            // accumulate info for the down slope ridges
            spectrum = new double[colCount];
            for (int col = 0; col < colCount; col++)
            {
                // i.e. for each frequency bin
                freqBin = MatrixTools.GetColumn(hits, col);
                int count = freqBin.Count(x => x == 2);
                spectrum[col] = count / (double)spanCount;
            }

            this.RngSpectrum = spectrum;
        }