/// <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)); }
/// <summary> /// returns the fft spectrum of a cross-correlation function. /// </summary> /// <param name="v1"></param> /// <param name="v2"></param> /// <returns></returns> public static double[] CrossCorr(double[] v1, double[] v2) { int n = v1.Length; // assume both vectors of same length double[] r; alglib.corrr1d(v1, n, v2, n, out r); // alglib.complex[] f; // alglib.fftr1d(newOp, out f); // System.LoggedConsole.WriteLine("{0}", alglib.ap.format(f, 3)); // for (int i = 0; i < op.Length; i++) LoggedConsole.WriteLine("{0} {1:f2}", i, op[i]); // rearrange corr output and NormaliseMatrixValues int xcorrLength = 2 * n; double[] xCorr = new double[xcorrLength]; // for (int i = 0; i < n - 1; i++) newOp[i] = r[i + n]; //rearrange corr output // for (int i = n - 1; i < opLength-1; i++) newOp[i] = r[i - n + 1]; for (int i = 0; i < n - 1; i++) { xCorr[i] = r[i + n] / (i + 1); // rearrange and NormaliseMatrixValues } for (int i = n - 1; i < xcorrLength - 1; i++) { xCorr[i] = r[i - n + 1] / (xcorrLength - i - 1); } // add extra value at end so have length = power of 2 for FFT // xCorr[xCorr.Length - 1] = xCorr[xCorr.Length - 2]; // LoggedConsole.WriteLine("xCorr length = " + xCorr.Length); // for (int i = 0; i < xCorr.Length; i++) LoggedConsole.WriteLine("{0} {1:f2}", i, xCorr[i]); // DataTools.writeBarGraph(xCorr); xCorr = DataTools.DiffFromMean(xCorr); FFT.WindowFunc wf = FFT.Hamming; var fft = new FFT(xCorr.Length, wf); var spectrum = fft.Invoke(xCorr); return(spectrum); }// CrossCorrelation()
public static Image AnalyseLocation(double[] signal, int sr, double startTimeInSeconds, int windowWidth) { int binCount = windowWidth / 2; int location = (int)Math.Round(startTimeInSeconds * sr); //assume location points to start of grunt if (location >= signal.Length) { LoggedConsole.WriteErrorLine("WARNING: Location is beyond end of signal."); return(null); } int nyquist = sr / 2; FFT.WindowFunc wf = FFT.Hamming; var fft = new FFT(windowWidth, wf); int maxHz = 1000; // max frequency to display in fft image double hzPerBin = nyquist / (double)binCount; int requiredBinCount = (int)Math.Round(maxHz / hzPerBin); double[] subsampleWav = DataTools.Subarray(signal, location, windowWidth); var spectrum = fft.Invoke(subsampleWav); // convert to power spectrum = DataTools.SquareValues(spectrum); spectrum = DataTools.filterMovingAverageOdd(spectrum, 3); spectrum = DataTools.normalise(spectrum); var subBandSpectrum = DataTools.Subarray(spectrum, 1, requiredBinCount); // ignore DC in bin zero. var startTime = TimeSpan.FromSeconds(startTimeInSeconds); double[] scoreArray = CalculateScores(subBandSpectrum, windowWidth); Image image4 = GraphsAndCharts.DrawWaveAndFft(subsampleWav, sr, startTime, spectrum, maxHz * 2, scoreArray); return(image4); }
public static Image <Rgb24> DrawWaveAndFft(double[] signal, int sr, TimeSpan startTime, double[] fftSpectrum, int maxHz, double[] scores) { int imageHeight = 300; double max = -2.0; foreach (double value in signal) { double absValue = Math.Abs(value); if (absValue > max) { max = absValue; } } double scalingFactor = 0.5 / max; // now process neighbourhood of each max int nyquist = sr / 2; int windowWidth = signal.Length; int binCount = windowWidth / 2; double hzPerBin = nyquist / (double)binCount; if (fftSpectrum == null) { FFT.WindowFunc wf = FFT.Hamming; var fft = new FFT(windowWidth, wf); fftSpectrum = fft.Invoke(signal); } int requiredBinCount = (int)(maxHz / hzPerBin); var subBandSpectrum = DataTools.Subarray(fftSpectrum, 1, requiredBinCount); // ignore DC in bin zero. var endTime = startTime + TimeSpan.FromSeconds(windowWidth / (double)sr); string title1 = $"Bandpass filtered: tStart={startTime.ToString()}, tEnd={endTime.ToString()}"; var image4A = DrawWaveform(title1, signal, signal.Length, imageHeight, scalingFactor); string title2 = $"FFT 1->{maxHz}Hz., hz/bin={hzPerBin:f1}, score={scores[0]:f3}={scores[1]:f3}+{scores[2]:f3}"; var image4B = DrawGraph(title2, subBandSpectrum, signal.Length, imageHeight, 1); var imageList = new List <Image <Rgb24> > { image4A, image4B }; Pen pen1 = new Pen(Color.Wheat, 1f); var stringFont = Drawing.Arial9; var bmp2 = new Image <Rgb24>(signal.Length, 25); bmp2.Mutate(g2 => { g2.DrawLine(pen1, 0, 0, signal.Length, 0); g2.DrawLine(pen1, 0, bmp2.Height - 1, signal.Length, bmp2.Height - 1); int barWidth = signal.Length / subBandSpectrum.Length; for (int i = 1; i < subBandSpectrum.Length - 1; i++) { if (subBandSpectrum[i] > subBandSpectrum[i - 1] && subBandSpectrum[i] > subBandSpectrum[i + 1]) { string label = $"{i + 1},"; g2.DrawText(label, stringFont, Color.Wheat, new PointF((i * barWidth) - 3, 3)); } } }); imageList.Add(bmp2); var image = ImageTools.CombineImagesVertically(imageList); return(image); }
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> /// <param name="audioRecording"></param> /// <param name="configuration"></param> /// <param name="segmentStartOffset"></param> /// <param name="getSpectralIndexes"></param> /// <param name="outputDirectory"></param> /// <param name="imageWidth"></param> /// <returns></returns> public override RecognizerResults Recognize(AudioRecording audioRecording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { const double minAmplitudeThreshold = 0.1; const int percentile = 5; const double scoreThreshold = 0.3; const bool doFiltering = true; const int windowWidth = 1024; const int signalBuffer = windowWidth * 2; //string path = @"C:\SensorNetworks\WavFiles\Freshwater\savedfortest.wav"; //audioRecording.Save(path); // this does not work int sr = audioRecording.SampleRate; int nyquist = audioRecording.Nyquist; // Get a value from the config file - with a backup default //int minHz = (int?)configuration[AnalysisKeys.MinHz] ?? 600; // Get a value from the config file - with no default, throw an exception if value is not present //int maxHz = ((int?)configuration[AnalysisKeys.MaxHz]).Value; // Get a value from the config file - without a string accessor, as a double //double someExampleSettingA = (double?)configuration.someExampleSettingA ?? 0.0; // common properties //string speciesName = (string)configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; //string abbreviatedSpeciesName = (string)configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; // min score for an acceptable event double eventThreshold = (double)configuration.GetDoubleOrNull(AnalysisKeys.EventThreshold); // get samples var samples = audioRecording.WavReader.Samples; double[] bandPassFilteredSignal = null; if (doFiltering) { // high pass filter int windowLength = 71; double[] highPassFilteredSignal; DSP_IIRFilter.ApplyMovingAvHighPassFilter(samples, windowLength, out highPassFilteredSignal); //DSP_IIRFilter filter2 = new DSP_IIRFilter("Chebyshev_Highpass_400"); //int order2 = filter2.order; //filter2.ApplyIIRFilter(samples, out highPassFilteredSignal); // Amplify 40dB and clip to +/-1.0; double factor = 100; // equiv to 20dB highPassFilteredSignal = DspFilters.AmplifyAndClip(highPassFilteredSignal, factor); //low pass filter string filterName = "Chebyshev_Lowpass_5000, scale*5"; DSP_IIRFilter filter = new DSP_IIRFilter(filterName); int order = filter.order; //System.LoggedConsole.WriteLine("\nTest " + filterName + ", order=" + order); filter.ApplyIIRFilter(highPassFilteredSignal, out bandPassFilteredSignal); } else // do not filter because already filtered - using Chris's filtered recording { bandPassFilteredSignal = samples; } // calculate an amplitude threshold that is above Nth percentile of amplitudes in the subsample int[] histogramOfAmplitudes; double minAmplitude; double maxAmplitude; double binWidth; int window = 66; Histogram.GetHistogramOfWaveAmplitudes(bandPassFilteredSignal, window, out histogramOfAmplitudes, out minAmplitude, out maxAmplitude, out binWidth); int percentileBin = Histogram.GetPercentileBin(histogramOfAmplitudes, percentile); double amplitudeThreshold = (percentileBin + 1) * binWidth; if (amplitudeThreshold < minAmplitudeThreshold) { amplitudeThreshold = minAmplitudeThreshold; } bool doAnalysisOfKnownExamples = true; if (doAnalysisOfKnownExamples) { // go to fixed location to check //1:02.07, 1:07.67, 1:12.27, 1:12.42, 1:12.59, 1:12.8, 1.34.3, 1:35.3, 1:40.16, 1:50.0, 2:05.9, 2:06.62, 2:17.57, 2:21.0 //2:26.33, 2:43.07, 2:43.15, 3:16.55, 3:35.09, 4:22.44, 4:29.9, 4:42.6, 4:51.48, 5:01.8, 5:21.15, 5:22.72, 5:32.37, 5.36.1, //5:42.82, 6:03.5, 6:19.93, 6:21.55, 6:42.0, 6:42.15, 6:46.44, 7:12.17, 7:42.65, 7:45.86, 7:46.18, 7:52.38, 7:59.11, 8:10.63, //8:14.4, 8:14.63, 8_15_240, 8_46_590, 8_56_590, 9_25_77, 9_28_94, 9_30_5, 9_43_9, 10_03_19, 10_24_26, 10_24_36, 10_38_8, //10_41_08, 10_50_9, 11_05_13, 11_08_63, 11_44_66, 11_50_36, 11_51_2, 12_04_93, 12_10_05, 12_20_78, 12_27_0, 12_38_5, //13_02_25, 13_08_18, 13_12_8, 13_25_24, 13_36_0, 13_50_4, 13_51_2, 13_57_87, 14_15_00, 15_09_74, 15_12_14, 15_25_79 //double[] times = { 2.2, 26.589, 29.62 }; //double[] times = { 2.2, 3.68, 10.83, 24.95, 26.589, 27.2, 29.62 }; //double[] times = { 2.2, 3.68, 10.83, 24.95, 26.589, 27.2, 29.62, 31.39, 62.1, 67.67, 72.27, 72.42, 72.59, 72.8, 94.3, 95.3, // 100.16, 110.0, 125.9, 126.62, 137.57, 141.0, 146.33, 163.07, 163.17, 196.55, 215.09, 262.44, 269.9, 282.6, // 291.48, 301.85, 321.18, 322.72, 332.37, 336.1, 342.82, 363.5, 379.93, 381.55, 402.0, 402.15, 406.44, 432.17, // 462.65, 465.86, 466.18, 472.38, 479.14, 490.63, 494.4, 494.63, 495.240, 526.590, 536.590, 565.82, 568.94, // 570.5, 583.9, 603.19, 624.26, 624.36, 638.8, 641.08, 650.9, 65.13, 68.63, 704.66, // 710.36, 711.2, 724.93, 730.05, 740.78, 747.05, 758.5, 782.25, 788.18, 792.8, // 805.24, 816.03, 830.4, 831.2, 837.87, 855.02, 909.74, 912.14, 925.81 }; var filePath = new FileInfo(@"C:\SensorNetworks\WavFiles\Freshwater\GruntSummaryRevisedAndEditedByMichael.csv"); List <CatFishCallData> data = Csv.ReadFromCsv <CatFishCallData>(filePath, true).ToList(); //var catFishCallDatas = data as IList<CatFishCallData> ?? data.ToList(); int count = data.Count(); var subSamplesDirectory = outputDirectory.CreateSubdirectory("testSubsamples_5000LPFilter"); //for (int t = 0; t < times.Length; t++) foreach (var fishCall in data) { //Image bmp1 = IctalurusFurcatus.AnalyseLocation(bandPassFilteredSignal, sr, times[t], windowWidth); // use following line where using time in seconds //int location = (int)Math.Round(times[t] * sr); //assume location points to start of grunt //double[] subsample = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 2 * signalBuffer); // use following line where using sample int location1 = fishCall.Sample / 2; //assume Chris's sample location points to centre of grunt. Divide by 2 because original recording was 44100. int location = (int)Math.Round(fishCall.TimeSeconds * sr); //assume location points to centre of grunt double[] subsample = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 2 * signalBuffer); // calculate an amplitude threshold that is above 95th percentile of amplitudes in the subsample //int[] histogramOfAmplitudes; //double minAmplitude; //double maxAmplitude; //double binWidth; //int window = 70; //int percentile = 90; //Histogram.GetHistogramOfWaveAmplitudes(subsample, window, out histogramOfAmplitudes, out minAmplitude, out maxAmplitude, out binWidth); //int percentileBin = Histogram.GetPercentileBin(histogramOfAmplitudes, percentile); //double amplitudeThreshold = (percentileBin + 1) * binWidth; //if (amplitudeThreshold < minAmplitudeThreshold) amplitudeThreshold = minAmplitudeThreshold; double[] scores1 = AnalyseWaveformAtLocation(subsample, amplitudeThreshold, scoreThreshold); string title1 = $"scores={fishCall.Timehms}"; Image bmp1 = GraphsAndCharts.DrawGraph(title1, scores1, subsample.Length, 300, 1); //bmp1.Save(path1.FullName); string title2 = $"tStart={fishCall.Timehms}"; Image bmp2 = GraphsAndCharts.DrawWaveform(title2, subsample, 1); var path1 = subSamplesDirectory.CombineFile($"scoresForTestSubsample_{fishCall.TimeSeconds}secs.png"); //var path2 = subSamplesDirectory.CombineFile($@"testSubsample_{times[t]}secs.wav.png"); Image[] imageList = { bmp2, bmp1 }; Image bmp3 = ImageTools.CombineImagesVertically(imageList); bmp3.Save(path1.FullName); //write wave form to txt file for later work in XLS //var path3 = subSamplesDirectory.CombineFile($@"testSubsample_{times[t]}secs.wav.csv"); //signalBuffer = 800; //double[] subsample2 = DataTools.Subarray(bandPassFilteredSignal, location - signalBuffer, 3 * signalBuffer); //FileTools.WriteArray2File(subsample2, path3.FullName); } } int signalLength = bandPassFilteredSignal.Length; // count number of 1000 sample segments int blockLength = 1000; int blockCount = signalLength / blockLength; int[] indexOfMax = new int[blockCount]; double[] maxInBlock = new double[blockCount]; for (int i = 0; i < blockCount; i++) { double max = -2.0; int blockStart = blockLength * i; for (int s = 0; s < blockLength; s++) { double absValue = Math.Abs(bandPassFilteredSignal[blockStart + s]); if (absValue > max) { max = absValue; maxInBlock[i] = max; indexOfMax[i] = blockStart + s; } } } // transfer max values to a list var indexList = new List <int>(); for (int i = 1; i < blockCount - 1; i++) { // only find the blocks that contain a max value that is > neighbouring blocks if (maxInBlock[i] > maxInBlock[i - 1] && maxInBlock[i] > maxInBlock[i + 1]) { indexList.Add(indexOfMax[i]); } //ALTERNATIVELY // look at max in each block //indexList.Add(indexOfMax[i]); } // now process neighbourhood of each max int binCount = windowWidth / 2; FFT.WindowFunc wf = FFT.Hamming; var fft = new FFT(windowWidth, wf); int maxHz = 1000; double hzPerBin = nyquist / (double)binCount; int requiredBinCount = (int)Math.Round(maxHz / hzPerBin); // init list of events List <AcousticEvent> events = new List <AcousticEvent>(); double[] scores = new double[signalLength]; // init of score array int id = 0; foreach (int location in indexList) { //System.LoggedConsole.WriteLine("Location " + location + ", id=" + id); int start = location - binCount; if (start < 0) { continue; } int end = location + binCount; if (end >= signalLength) { continue; } double[] subsampleWav = DataTools.Subarray(bandPassFilteredSignal, start, windowWidth); var spectrum = fft.Invoke(subsampleWav); // convert to power spectrum = DataTools.SquareValues(spectrum); spectrum = DataTools.filterMovingAverageOdd(spectrum, 3); spectrum = DataTools.normalise(spectrum); var subBandSpectrum = DataTools.Subarray(spectrum, 1, requiredBinCount); // ignore DC in bin zero. // now do some tests on spectrum to determine if it is a candidate grunt bool eventFound = false; double[] scoreArray = CalculateScores(subBandSpectrum, windowWidth); double score = scoreArray[0]; if (score > scoreThreshold) { eventFound = true; } if (eventFound) { for (int i = location - binCount; i < location + binCount; i++) { scores[location] = score; } var startTime = TimeSpan.FromSeconds((location - binCount) / (double)sr); string startLabel = startTime.Minutes + "." + startTime.Seconds + "." + startTime.Milliseconds; Image image4 = GraphsAndCharts.DrawWaveAndFft(subsampleWav, sr, startTime, spectrum, maxHz * 2, scoreArray); var path4 = outputDirectory.CreateSubdirectory("subsamples").CombineFile($@"subsample_{location}_{startLabel}.png"); image4.Save(path4.FullName); // have an event, store the data in the AcousticEvent class double duration = 0.2; int minFreq = 50; int maxFreq = 1000; var anEvent = new AcousticEvent(segmentStartOffset, startTime.TotalSeconds, duration, minFreq, maxFreq); anEvent.Name = "grunt"; //anEvent.Name = DataTools.WriteArrayAsCsvLine(subBandSpectrum, "f4"); anEvent.Score = score; events.Add(anEvent); } id++; } // make a spectrogram var config = new SonogramConfig { NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0, }; var sonogram = (BaseSonogram) new SpectrogramStandard(config, audioRecording.WavReader); //// when the value is accessed, the indices are calculated //var indices = getSpectralIndexes.Value; //// check if the indices have been calculated - you shouldn't actually need this //if (getSpectralIndexes.IsValueCreated) //{ // // then indices have been calculated before //} var plot = new Plot(this.DisplayName, scores, eventThreshold); return(new RecognizerResults() { Events = events, Hits = null, //ScoreTrack = null, Plots = plot.AsList(), Sonogram = sonogram, }); }
/// <summary> /// Returns the following 18 values encapsulated in class EnvelopeAndFft /// 1) the minimum and maximum signal values /// 2) the average of absolute amplitudes for each frame /// 3) the minimum value in each frame /// 3) the maximum value in each frame. /// 3) the signal envelope as vector. i.e. the maximum of absolute amplitudes for each frame. /// 4) vector of frame energies /// 5) the high amplitude and clipping counts /// 6) the signal amplitude spectrogram /// 7) the power of the FFT Window, i.e. sum of squared window values. /// 8) the nyquist /// 9) the width of freq bin in Hz /// 10) the Nyquist bin ID /// AND OTHERS /// The returned info is used by Sonogram classes to draw sonograms and by Spectral Indices classes to calculate Spectral indices. /// Less than half the info is used to draw sonograms but it is difficult to disentangle calculation of all the info without /// reverting back to the old days when we used two classes and making sure they remain in synch. /// </summary> public static EnvelopeAndFft ExtractEnvelopeAndAmplSpectrogram( double[] signal, int sampleRate, double epsilon, int frameSize, int frameStep, string windowName = null) { // SIGNAL PRE-EMPHASIS helps with speech signals // Do not use this for environmental audio //if (config.DoPreemphasis) //{ // signal = DSP_Filters.PreEmphasis(signal, 0.96); //} int[,] frameIDs = FrameStartEnds(signal.Length, frameSize, frameStep); if (frameIDs == null) { throw new NullReferenceException("Thrown in EnvelopeAndFft.ExtractEnvelopeAndAmplSpectrogram(): int matrix, frameIDs, cannot be null."); } int frameCount = frameIDs.GetLength(0); // set up the FFT parameters if (windowName == null) { windowName = FFT.KeyHammingWindow; } FFT.WindowFunc w = FFT.GetWindowFunction(windowName); var fft = new FFT(frameSize, w); // init class which calculates the Matlab compatible .NET FFT double[,] spectrogram = new double[frameCount, fft.CoeffCount]; // init amplitude sonogram double minSignalValue = double.MaxValue; double maxSignalValue = double.MinValue; double[] average = new double[frameCount]; double[] minValues = new double[frameCount]; double[] maxValues = new double[frameCount]; double[] envelope = new double[frameCount]; double[] frameEnergy = new double[frameCount]; double[] frameDecibels = new double[frameCount]; // for all frames for (int i = 0; i < frameCount; i++) { int start = i * frameStep; int end = start + frameSize; // get average and envelope for current frame double frameMin = signal[start]; double frameMax = signal[start]; double frameSum = signal[start]; double total = Math.Abs(signal[start]); double maxAbsValue = total; double energy = 0; // for all values in frame for (int x = start + 1; x < end; x++) { if (signal[x] > maxSignalValue) { maxSignalValue = signal[x]; } if (signal[x] < minSignalValue) { minSignalValue = signal[x]; } frameSum += signal[x]; // Get frame min and max if (signal[x] < frameMin) { frameMin = signal[x]; } if (signal[x] > frameMax) { frameMax = signal[x]; } energy += signal[x] * signal[x]; // Get absolute signal average in current frame double absValue = Math.Abs(signal[x]); total += absValue; // Get the maximum absolute signal value in current frame if (absValue > maxAbsValue) { maxAbsValue = absValue; } } // end of frame double frameDc = frameSum / frameSize; minValues[i] = frameMin; maxValues[i] = frameMax; average[i] = total / frameSize; envelope[i] = maxAbsValue; frameEnergy[i] = energy / frameSize; frameDecibels[i] = 10 * Math.Log10(frameEnergy[i]); // remove DC value from signal values double[] signalMinusAv = new double[frameSize]; for (int j = 0; j < frameSize; j++) { signalMinusAv[j] = signal[start + j] - frameDc; } // generate the spectra of FFT AMPLITUDES - NOTE: f[0]=DC; f[64]=Nyquist var f1 = fft.InvokeDotNetFFT(signalMinusAv); // Previous alternative call to do the FFT and return amplitude spectrum //f1 = fft.Invoke(window); // Smooth spectrum to reduce variance // In the early days (pre-2010), we used to smooth the spectra to reduce sonogram variance. This is statistically correct thing to do. // Later, we stopped this for standard sonograms but kept it for calculating acoustic indices. // As of 28 March 2017, we are merging the two codes and keeping spectrum smoothing. // Will need to check the effect on spectrograms. int smoothingWindow = 3; f1 = DataTools.filterMovingAverage(f1, smoothingWindow); // transfer amplitude spectrum to spectrogram matrix for (int j = 0; j < fft.CoeffCount; j++) { spectrogram[i, j] = f1[j]; } } // end frames // Remove the DC column ie column zero from amplitude spectrogram. double[,] amplitudeSpectrogram = MatrixTools.Submatrix(spectrogram, 0, 1, spectrogram.GetLength(0) - 1, spectrogram.GetLength(1) - 1); // check the envelope for clipping. Accept a clip if two consecutive frames have max value = 1,0 Clipping.GetClippingCount(signal, envelope, frameStep, epsilon, out int highAmplitudeCount, out int clipCount); // get SNR data var snrData = new SNR(signal, frameIDs); return(new EnvelopeAndFft { // The following data is required when constructing sonograms Duration = TimeSpan.FromSeconds((double)signal.Length / sampleRate), Epsilon = epsilon, SampleRate = sampleRate, FrameCount = frameCount, FractionOfHighEnergyFrames = snrData.FractionOfHighEnergyFrames, WindowPower = fft.WindowPower, AmplitudeSpectrogram = amplitudeSpectrogram, // The below 11 variables are only used when calculating spectral and summary indices // energy level information ClipCount = clipCount, HighAmplitudeCount = highAmplitudeCount, MinSignalValue = minSignalValue, MaxSignalValue = maxSignalValue, // envelope info Average = average, MinFrameValues = minValues, MaxFrameValues = maxValues, Envelope = envelope, FrameEnergy = frameEnergy, FrameDecibels = frameDecibels, // freq scale info NyquistFreq = sampleRate / 2, NyquistBin = amplitudeSpectrogram.GetLength(1) - 1, FreqBinWidth = sampleRate / (double)amplitudeSpectrogram.GetLength(1) / 2, }); }
public static double[] GetOscillationArrayUsingCwt(double[,] xCorrByTimeMatrix, double framesPerSecond, int binNumber) { int xCorrLength = xCorrByTimeMatrix.GetLength(0); //int sampleCount = xCorrByTimeMatrix.GetLength(1); // loop over the singular values and // transfer data from SVD.UMatrix to a single vector of oscilation values var oscillationsVector = new double[xCorrLength / 2]; for (int e = 0; e < 10; e++) { var autocor = new double[xCorrLength]; // the sign of the left singular vectors are usually negative. if (autocor[0] < 0) { for (int i = 0; i < autocor.Length; i++) { autocor[i] *= -1.0; } } // ##########################################################\ 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.5; spectrum = DataTools.SquareValues(spectrum); double avPower = spectrum.Sum() / spectrum.Length; int maxIndex = DataTools.GetMaxIndex(spectrum); double powerAtMax = spectrum[maxIndex]; //double relativePower1 = powerAtMax / sumOfSquares; double relativePower2 = powerAtMax / avPower; //if (relativePower1 > 0.05) if (relativePower2 > 10.0) { // check for boundary overrun if (maxIndex < oscillationsVector.Length) { // add in a new oscillation //oscillationsVector[maxIndex] += powerAtMax; oscillationsVector[maxIndex] += relativePower2; } } } 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); }
/// <summary> /// reduces the sequence of Xcorrelation vectors to a single summary vector. /// Does this by: /// (1) do SVD on the collection of XCORRELATION vectors /// (2) select the dominant ones based on the eigen values - 90% threshold /// Typically there are 1 to 10 eigen values depending on how busy the bin is. /// (3) Do an FFT on each of the returned SVD vectors to pick the dominant oscillation rate. /// (4) Accumulate the oscillations in a freq by oscillation rate matrix. /// The amplitude value for the oscillation is the eigenvalue. /// # /// NOTE: There should only be one dominant oscillation in any one freq band at one time. /// Birds with oscillating calls do call simultaneously, but this technique will only pick up the dominant call. /// #. /// </summary> /// <param name="xCorrByTimeMatrix">double[,] xCorrelationsByTime = new double[sampleLength, sampleCount]. </param> /// <param name="sensitivity">can't remember what this does.</param> /// <param name="binNumber">only used when debugging.</param> public static double[] GetOscillationArrayUsingSvdAndFft(double[,] xCorrByTimeMatrix, double sensitivity, int binNumber) { int xCorrLength = xCorrByTimeMatrix.GetLength(0); // int sampleCount = xCorrByTimeMatrix.GetLength(1); // do singular value decomp on the xcorrelation vectors. // we want to compute the U and V matrices of singular vectors. //var svd = DenseMatrix.OfArray(xCorrByTimeMatrix).Svd(true); var svd = DenseMatrix.OfArray(xCorrByTimeMatrix).Svd(); // svd.S returns the singular values in a vector Vector <double> singularValues = svd.S; // get total energy in first singular values double energySum = 0.0; foreach (double v in singularValues) { energySum += v * v; } // get the 90% most significant ####### THis is a significant parameter but not critical. 90% is OK double significanceThreshold = 0.9; double energy = 0.0; int countOfSignificantSingularValues = 0; for (int n = 0; n < singularValues.Count; n++) { energy += singularValues[n] * singularValues[n]; double fraction = energy / energySum; if (fraction > significanceThreshold) { countOfSignificantSingularValues = n + 1; break; } } //foreach (double d in singularValues) // Console.WriteLine("singular value = {0}", d); //Console.WriteLine("Freq bin:{0} Count Of Significant SingularValues = {1}", binNumber, countOfSignificantSingularValues); // svd.U returns the LEFT singular vectors in matrix Matrix <double> uMatrix = svd.U; //Matrix<double> relevantU = UMatrix.SubMatrix(0, UMatrix.RowCount-1, 0, eigenVectorCount); //Console.WriteLine("\n\n"); //MatrixTools.writeMatrix(UMatrix.ToArray()); //string pathUmatrix1 = @"C:\SensorNetworks\Output\Sonograms\testMatrixSVD_U1.png"; //ImageTools.DrawReversedMDNMatrix(UMatrix, pathUmatrix1); //string pathUmatrix2 = @"C:\SensorNetworks\Output\Sonograms\testMatrixSVD_U2.png"; //ImageTools.DrawReversedMDNMatrix(relevantU, pathUmatrix2); // loop over the singular values and // transfer data from SVD.UMatrix to a single vector of oscilation values double[] oscillationsVector = new double[xCorrLength / 2]; for (int e = 0; e < countOfSignificantSingularValues; e++) { double[] autocor = uMatrix.Column(e).ToArray(); // the sign of the left singular vectors are usually negative. if (autocor[0] < 0) { for (int i = 0; i < autocor.Length; i++) { autocor[i] *= -1.0; } } // ##########################################################\ 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.5; spectrum = DataTools.SquareValues(spectrum); // get relative power in the three bins around max. double sumOfSquares = spectrum.Sum(); 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 oscillationsVector if (relativePower > sensitivity) { oscillationsVector[maxIndex] += powerAtMax; } } return(LogTransformOscillationVector(oscillationsVector, countOfSignificantSingularValues)); }
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); }