/// <summary> /// This method averages dB log values incorrectly but it is faster than doing many log conversions. /// This method is used to find acoustic events and is accurate enough for the purpose. /// </summary> public static (List <EventCommon> ListOfevents, double[] CombinedIntensityArray) GetOnebinTracks( SpectrogramStandard sonogram, OnebinTrackParameters parameters, TimeSpan segmentStartOffset) { var sonogramData = sonogram.Data; int frameCount = sonogramData.GetLength(0); int binCount = sonogramData.GetLength(1); int nyquist = sonogram.NyquistFrequency; double binWidth = nyquist / (double)binCount; int minBin = (int)Math.Round(parameters.MinHertz.Value / binWidth); int maxBin = (int)Math.Round(parameters.MaxHertz.Value / binWidth); double decibelThreshold = parameters.DecibelThreshold.Value; double minDuration = parameters.MinDuration.Value; double maxDuration = parameters.MaxDuration.Value; var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, frameSize: sonogram.Configuration.WindowSize, frameOverlap: sonogram.Configuration.WindowOverlap); //Find all bin peaks and place in peaks matrix var peaks = new double[frameCount, binCount]; for (int tf = 0; tf < frameCount; tf++) { for (int bin = minBin + 1; bin < maxBin - 1; bin++) { if (sonogramData[tf, bin] < decibelThreshold) { continue; } // here we define the amplitude profile of a whistle. The buffer zone around whistle is five bins wide. var bandIntensity = ((sonogramData[tf, bin - 1] * 0.5) + sonogramData[tf, bin] + (sonogramData[tf, bin + 1] * 0.5)) / 2.0; var topSidebandIntensity = (sonogramData[tf, bin + 3] + sonogramData[tf, bin + 4] + sonogramData[tf, bin + 5]) / 3.0; var netAmplitude = 0.0; if (bin < 4) { netAmplitude = bandIntensity - topSidebandIntensity; } else { var bottomSideBandIntensity = (sonogramData[tf, bin - 3] + sonogramData[tf, bin - 4] + sonogramData[tf, bin - 5]) / 3.0; netAmplitude = bandIntensity - ((topSidebandIntensity + bottomSideBandIntensity) / 2.0); } if (netAmplitude >= decibelThreshold) { peaks[tf, bin] = sonogramData[tf, bin]; } } } var tracks = GetOnebinTracks(peaks, minDuration, maxDuration, decibelThreshold, converter); // Initialise tracks as events and get the combined intensity array. var events = new List <WhistleEvent>(); var combinedIntensityArray = new double[frameCount]; var scoreRange = new Interval <double>(0, decibelThreshold * 5); foreach (var track in tracks) { var ae = new WhistleEvent(track, scoreRange) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, Name = "Whistle", }; events.Add(ae); // fill the intensity array var startRow = converter.FrameFromStartTime(track.StartTimeSeconds); var amplitudeTrack = track.GetAmplitudeOverTimeFrames(); for (int i = 0; i < amplitudeTrack.Length; i++) { combinedIntensityArray[startRow + i] = Math.Max(combinedIntensityArray[startRow + i], amplitudeTrack[i]); } } // This algorithm tends to produce temporally overlapped whistle events in adjacent channels. // Combine overlapping whistle events var hertzDifference = 4 * binWidth; var whistleEvents = WhistleEvent.CombineAdjacentWhistleEvents(events, hertzDifference); // Finally filter the whistles for presense of excess noise in buffer band just above the whistle. // Excess noise would suggest this is not a whistle event. var bufferHertz = 300; var bufferBins = (int)Math.Round(bufferHertz / binWidth); var filteredEvents = new List <EventCommon>(); foreach (var ev in whistleEvents) { var avNhAmplitude = GetAverageAmplitudeInNeighbourhood((SpectralEvent)ev, sonogramData, bufferBins, converter); Console.WriteLine($"###################################Buffer Average decibels = {avNhAmplitude}"); if (avNhAmplitude < decibelThreshold) { // There is little acoustic activity in the buffer zone above the whistle. It is likely to be a whistle. filteredEvents.Add(ev); } } return(filteredEvents, combinedIntensityArray); }
public void TestOnebinTrackAlgorithm() { // Set up the recognizer parameters. var parameters = new OnebinTrackParameters() { MinHertz = 500, MaxHertz = 6000, MinDuration = 0.2, MaxDuration = 1.1, DecibelThreshold = 2.0, CombinePossibleSyllableSequence = false, //SyllableStartDifference = TimeSpan.FromSeconds(0.2), //SyllableHertzGap = 300, }; //Set up the virtual recording. int samplerate = 22050; double signalDuration = 13.0; //seconds var segmentStartOffset = TimeSpan.FromSeconds(60.0); // set up the config for a virtual spectrogram. var sonoConfig = new SonogramConfig() { WindowSize = 512, WindowStep = 512, WindowOverlap = 0.0, // this must be set WindowFunction = WindowFunctions.HANNING.ToString(), NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = 0.0, Duration = TimeSpan.FromSeconds(signalDuration), SampleRate = samplerate, }; var spectrogram = this.CreateArtificialSpectrogramToTestTracksAndHarmonics(sonoConfig); //var image1 = SpectrogramTools.GetSonogramPlusCharts(spectrogram, null, null, null); //results.Sonogram.GetImage().Save(this.outputDirectory + "\\debug.png"); var plots = new List <Plot>(); var(spectralEvents, dBArray) = OnebinTrackAlgorithm.GetOnebinTracks( spectrogram, parameters, segmentStartOffset); // draw a plot of max decibels in each frame double decibelNormalizationMax = 5 * parameters.DecibelThreshold.Value; var dBThreshold = parameters.DecibelThreshold.Value / decibelNormalizationMax; var normalisedDecibelArray = DataTools.NormaliseInZeroOne(dBArray, 0, decibelNormalizationMax); var plot1 = new Plot("decibel max", normalisedDecibelArray, dBThreshold); plots.Add(plot1); var allResults = new RecognizerResults() { NewEvents = new List <EventCommon>(), Hits = null, ScoreTrack = null, Plots = new List <Plot>(), Sonogram = null, }; // combine the results i.e. add the events list of call events. allResults.NewEvents.AddRange(spectralEvents); allResults.Plots.AddRange(plots); allResults.Sonogram = spectrogram; // DEBUG PURPOSES this.SaveTestOutput( outputDirectory => GenericRecognizer.SaveDebugSpectrogram(allResults, null, outputDirectory, "WhistleTrack")); //NOTE: There are 16 whistles in the test spectrogram ... // but three of them are too weak to be detected at this threshold. Assert.AreEqual(13, allResults.NewEvents.Count); var @event = (SpectralEvent)allResults.NewEvents[0]; Assert.AreEqual(60 + 0.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(60 + 0.35, @event.EventEndSeconds, 0.1); Assert.AreEqual(2150, @event.LowFrequencyHertz); Assert.AreEqual(2193, @event.HighFrequencyHertz); @event = (SpectralEvent)allResults.NewEvents[4]; Assert.AreEqual(60 + 5.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(60 + 6.0, @event.EventEndSeconds, 0.1); Assert.AreEqual(989, @event.LowFrequencyHertz); Assert.AreEqual(1032, @event.HighFrequencyHertz); @event = (SpectralEvent)allResults.NewEvents[11]; Assert.AreEqual(60 + 11.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(60 + 12.0, @event.EventEndSeconds, 0.1); Assert.AreEqual(989, @event.LowFrequencyHertz); Assert.AreEqual(1032, @event.HighFrequencyHertz); }