public void TestEventMerging() { // make a list of events var events = new List <AcousticEvent>(); double maxPossibleScore = 10.0; var event1 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 1.0, eventDuration: 5.0, minFreq: 1000, maxFreq: 8000) { Name = "Event1", Score = 1.0, ScoreNormalised = 1.0 / maxPossibleScore, }; events.Add(event1); var event2 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 4.5, eventDuration: 2.0, minFreq: 1500, maxFreq: 6000) { Name = "Event2", Score = 5.0, ScoreNormalised = 5.0 / maxPossibleScore, }; events.Add(event2); var event3 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 7.0, eventDuration: 2.0, minFreq: 1000, maxFreq: 8000) { Name = "Event3", Score = 9.0, ScoreNormalised = 9.0 / maxPossibleScore, }; events.Add(event3); // combine adjacent acoustic events events = AcousticEvent.CombineOverlappingEvents(events: events, segmentStartOffset: TimeSpan.Zero); Assert.AreEqual(2, events.Count); Assert.AreEqual(1.0, events[0].EventStartSeconds, 1E-4); Assert.AreEqual(6.5, events[0].EventEndSeconds, 1E-4); Assert.AreEqual(7.0, events[1].EventStartSeconds, 1E-4); Assert.AreEqual(9.0, events[1].EventEndSeconds, 1E-4); Assert.AreEqual(1000, events[0].LowFrequencyHertz); Assert.AreEqual(8000, events[0].HighFrequencyHertz); Assert.AreEqual(1000, events[1].LowFrequencyHertz); Assert.AreEqual(8000, events[1].HighFrequencyHertz); Assert.AreEqual(5.0, events[0].Score, 1E-4); Assert.AreEqual(9.0, events[1].Score, 1E-4); Assert.AreEqual(0.5, events[0].ScoreNormalised, 1E-4); Assert.AreEqual(0.9, events[1].ScoreNormalised, 1E-4); }
/// <inheritdoc/> public override RecognizerResults Recognize( AudioRecording audioRecording, Config genericConfig, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { var configuration = (GenericRecognizerConfig)genericConfig; if (configuration.Profiles.NotNull() && configuration.Profiles.Count == 0) { throw new ConfigFileException( "The generic recognizer needs at least one profile set. 0 were found."); } int count = configuration.Profiles.Count; var message = $"Found {count} analysis profile(s): " + configuration.Profiles.Keys.Join(", "); Log.Info(message); var allResults = new RecognizerResults() { Events = new List <AcousticEvent>(), Hits = null, ScoreTrack = null, Plots = new List <Plot>(), Sonogram = null, }; // Now process each of the profiles foreach (var(profileName, profileConfig) in configuration.Profiles) { Log.Info("Processing profile: " + profileName); List <AcousticEvent> acousticEvents; var plots = new List <Plot>(); SpectrogramStandard sonogram; Log.Debug($"Using the {profileName} algorithm... "); if (profileConfig is CommonParameters parameters) { if (profileConfig is BlobParameters || profileConfig is OscillationParameters || profileConfig is WhistleParameters || profileConfig is HarmonicParameters || profileConfig is SpectralPeakTrackParameters || profileConfig is VerticalTrackParameters || profileConfig is ClickParameters) { sonogram = new SpectrogramStandard(ParametersToSonogramConfig(parameters), audioRecording.WavReader); if (profileConfig is OscillationParameters op) { Oscillations2012.Execute( sonogram, op.MinHertz.Value, op.MaxHertz.Value, op.DctDuration, op.MinOscillationFrequency, op.MaxOscillationFrequency, op.DctThreshold, op.EventThreshold, op.MinDuration.Value, op.MaxDuration.Value, out var scores, out acousticEvents, out var hits, segmentStartOffset); //plots.Add(new Plot($"{profileName} (:OscillationScore)", scores, op.EventThreshold)); var plot = PreparePlot(scores, $"{profileName} (:OscillationScore)", op.EventThreshold); plots.Add(plot); } else if (profileConfig is BlobParameters bp) { //get the array of intensity values minus intensity in side/buffer bands. //i.e. require silence in side-bands. Otherwise might simply be getting part of a broader band acoustic event. var decibelArray = SNR.CalculateFreqBandAvIntensityMinusBufferIntensity( sonogram.Data, bp.MinHertz.Value, bp.MaxHertz.Value, bp.BottomHertzBuffer.Value, bp.TopHertzBuffer.Value, sonogram.NyquistFrequency); // prepare plot of resultant blob decibel array. var plot = PreparePlot(decibelArray, $"{profileName} (Blob:db Intensity)", bp.DecibelThreshold.Value); plots.Add(plot); // iii: CONVERT blob decibel SCORES TO ACOUSTIC EVENTS. // Note: This method does NOT do prior smoothing of the dB array. acousticEvents = AcousticEvent.GetEventsAroundMaxima( decibelArray, segmentStartOffset, bp.MinHertz.Value, bp.MaxHertz.Value, bp.DecibelThreshold.Value, TimeSpan.FromSeconds(bp.MinDuration.Value), TimeSpan.FromSeconds(bp.MaxDuration.Value), sonogram.FramesPerSecond, sonogram.FBinWidth); } else if (profileConfig is WhistleParameters wp) { //get the array of intensity values minus intensity in side/buffer bands. double[] decibelArray; (acousticEvents, decibelArray) = WhistleParameters.GetWhistles( sonogram, wp.MinHertz.Value, wp.MaxHertz.Value, sonogram.NyquistFrequency, wp.DecibelThreshold.Value, wp.MinDuration.Value, wp.MaxDuration.Value, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (Whistle:dB Intensity)", wp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is HarmonicParameters hp) { double[] decibelMaxArray; double[] harmonicIntensityScores; (acousticEvents, decibelMaxArray, harmonicIntensityScores) = HarmonicParameters.GetComponentsWithHarmonics( sonogram, hp.MinHertz.Value, hp.MaxHertz.Value, sonogram.NyquistFrequency, hp.DecibelThreshold.Value, hp.DctThreshold.Value, hp.MinDuration.Value, hp.MaxDuration.Value, hp.MinFormantGap.Value, hp.MaxFormantGap.Value, segmentStartOffset); var plot = PreparePlot(harmonicIntensityScores, $"{profileName} (Harmonics:dct intensity)", hp.DctThreshold.Value); plots.Add(plot); } else if (profileConfig is SpectralPeakTrackParameters tp) { double[] decibelArray; (acousticEvents, decibelArray) = SpectralPeakTrackParameters.GetSpectralPeakTracks( sonogram, tp.MinHertz.Value, tp.MaxHertz.Value, sonogram.NyquistFrequency, tp.DecibelThreshold.Value, tp.MinDuration.Value, tp.MaxDuration.Value, tp.CombinePossibleHarmonics, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (SpectralPeaks:dB Intensity)", tp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is ClickParameters cp) { double[] decibelArray; (acousticEvents, decibelArray) = ClickParameters.GetClicks( sonogram, cp.MinHertz.Value, cp.MaxHertz.Value, sonogram.NyquistFrequency, cp.DecibelThreshold.Value, cp.MinBandwidthHertz.Value, cp.MaxBandwidthHertz.Value, cp.CombineProximalSimilarEvents, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (Click:dB Intensity)", cp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is VerticalTrackParameters vtp) { double[] decibelArray; (acousticEvents, decibelArray) = VerticalTrackParameters.GetVerticalTracks( sonogram, vtp.MinHertz.Value, vtp.MaxHertz.Value, sonogram.NyquistFrequency, vtp.DecibelThreshold.Value, vtp.MinBandwidthHertz.Value, vtp.MaxBandwidthHertz.Value, vtp.CombineProximalSimilarEvents, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (VerticalTrack:dB Intensity)", vtp.DecibelThreshold.Value); plots.Add(plot); } else { throw new InvalidOperationException(); } } else { throw new InvalidOperationException(); } //iV add additional info to the acoustic events acousticEvents.ForEach(ae => { ae.FileName = audioRecording.BaseName; ae.SpeciesName = parameters.SpeciesName; ae.Name = parameters.ComponentName; ae.Profile = profileName; ae.SegmentDurationSeconds = audioRecording.Duration.TotalSeconds; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.SetTimeAndFreqScales(sonogram.FrameStep, sonogram.FrameDuration, sonogram.FBinWidth); }); } else if (profileConfig is Aed.AedConfiguration ac) { var config = new SonogramConfig { NoiseReductionType = ac.NoiseReductionType, NoiseReductionParameter = ac.NoiseReductionParameter, }; sonogram = new SpectrogramStandard(config, audioRecording.WavReader); acousticEvents = Aed.CallAed(sonogram, ac, segmentStartOffset, audioRecording.Duration).ToList(); } else { throw new InvalidOperationException(); } // combine the results i.e. add the events list of call events. allResults.Events.AddRange(acousticEvents); allResults.Plots.AddRange(plots); // effectively keeps only the *last* sonogram produced allResults.Sonogram = sonogram; Log.Debug($"{profileName} event count = {acousticEvents.Count}"); // DEBUG PURPOSES COMMENT NEXT LINE //SaveDebugSpectrogram(allResults, genericConfig, outputDirectory, "name"); } // combine adjacent acoustic events if (this.combineOverlappedEvents) { allResults.Events = AcousticEvent.CombineOverlappingEvents(allResults.Events, segmentStartOffset); } return(allResults); }
/// <summary> /// Calculates the mean intensity in a freq band defined by its min and max freq. /// 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 <AcousticEvent>, double[]) GetWhistles( SpectrogramStandard sonogram, int minHz, int maxHz, int nyquist, double decibelThreshold, double minDuration, double maxDuration, TimeSpan segmentStartOffset) { var sonogramData = sonogram.Data; int frameCount = sonogramData.GetLength(0); int binCount = sonogramData.GetLength(1); double binWidth = nyquist / (double)binCount; int minBin = (int)Math.Round(minHz / binWidth); int maxBin = (int)Math.Round(maxHz / binWidth); // list of accumulated acoustic events var events = new List <AcousticEvent>(); var combinedIntensityArray = new double[frameCount]; // for all frequency bins except top and bottom for (int bin = minBin + 1; bin < maxBin; bin++) { // set up an intensity array for the frequency bin. double[] intensity = new double[frameCount]; // buffer zone around whistle is four bins wide. if (minBin < 4) { // for all time frames in this frequency bin for (int t = 0; t < frameCount; t++) { var bandIntensity = (sonogramData[t, bin - 1] + sonogramData[t, bin] + sonogramData[t, bin + 1]) / 3.0; var topSideBandIntensity = (sonogramData[t, bin + 3] + sonogramData[t, bin + 4] + sonogramData[t, bin + 5]) / 3.0; intensity[t] = bandIntensity - topSideBandIntensity; intensity[t] = Math.Max(0.0, intensity[t]); } } else { // for all time frames in this frequency bin for (int t = 0; t < frameCount; t++) { var bandIntensity = (sonogramData[t, bin - 1] + sonogramData[t, bin] + sonogramData[t, bin + 1]) / 3.0; var topSideBandIntensity = (sonogramData[t, bin + 3] + sonogramData[t, bin + 4] + sonogramData[t, bin + 5]) / 6.0; var bottomSideBandIntensity = (sonogramData[t, bin - 3] + sonogramData[t, bin - 4] + sonogramData[t, bin - 5]) / 6.0; intensity[t] = bandIntensity - topSideBandIntensity - bottomSideBandIntensity; intensity[t] = Math.Max(0.0, intensity[t]); } } // smooth the decibel array to allow for brief gaps. intensity = DataTools.filterMovingAverageOdd(intensity, 7); //calculate the Hertz bounds of the acoustic events for these freq bins int bottomHzBound = (int)Math.Floor(sonogram.FBinWidth * (bin - 1)); int topHzBound = (int)Math.Ceiling(sonogram.FBinWidth * (bin + 2)); //extract the events based on length and threshhold. // Note: This method does NOT do prior smoothing of the dB array. var acousticEvents = AcousticEvent.ConvertScoreArray2Events( intensity, bottomHzBound, topHzBound, sonogram.FramesPerSecond, sonogram.FBinWidth, decibelThreshold, minDuration, maxDuration, segmentStartOffset); // add to conbined intensity array for (int t = 0; t < frameCount; t++) { //combinedIntensityArray[t] += intensity[t]; combinedIntensityArray[t] = Math.Max(intensity[t], combinedIntensityArray[t]); } // combine events events.AddRange(acousticEvents); } //end for all freq bins // combine adjacent acoustic events events = AcousticEvent.CombineOverlappingEvents(events, segmentStartOffset); return(events, combinedIntensityArray); }