// ############################################################################################################################# // ################################# 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); }
/// <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); }
/// <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); }
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}"); }
/// <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)); }
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)); }
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); }
}//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); }
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); }
/// <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); }
/// <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; }