From e45a575fdfa0e5d3561a3d61bc3e6caa933f49ed Mon Sep 17 00:00:00 2001 From: towsey Date: Thu, 24 Sep 2020 15:25:24 +1000 Subject: [PATCH] Refactor methods for recognition of the main generic acoustic events. Issue #370 Main reason for refactoring is so that the generic component recognizers are able to do multiple passes ofr an array of different decibel threshold values. --- src/AudioAnalysisTools/HarmonicParameters.cs | 2 +- .../Ocillations/Oscillations2012.cs | 2 +- .../Tracks/ForwardTrackAlgorithm.cs | 14 ++--- .../Tracks/OnebinTrackAlgorithm.cs | 30 +++++++---- .../Tracks/OneframeTrackAlgorithm.cs | 53 ++++++++++++------- .../Tracks/UpwardTrackAlgorithm.cs | 37 ++++++++----- 6 files changed, 88 insertions(+), 50 deletions(-) diff --git a/src/AudioAnalysisTools/HarmonicParameters.cs b/src/AudioAnalysisTools/HarmonicParameters.cs index 41eb3a39e..e64665735 100644 --- a/src/AudioAnalysisTools/HarmonicParameters.cs +++ b/src/AudioAnalysisTools/HarmonicParameters.cs @@ -66,7 +66,7 @@ public static (List SpectralEvents, List DecibelPlots) GetCom segmentStartOffset); // prepare plot of resultant Harmonics decibel array. - var plot = Plot.PreparePlot(decibelMaxArray, $"{profileName} (Harmonics:{threshold:d0}db)", threshold.Value); + var plot = Plot.PreparePlot(decibelMaxArray, $"{profileName} (Harmonics:{threshold:F0}db)", threshold.Value); plots.Add(plot); } diff --git a/src/AudioAnalysisTools/Ocillations/Oscillations2012.cs b/src/AudioAnalysisTools/Ocillations/Oscillations2012.cs index 3ed499d02..20e5de02b 100644 --- a/src/AudioAnalysisTools/Ocillations/Oscillations2012.cs +++ b/src/AudioAnalysisTools/Ocillations/Oscillations2012.cs @@ -57,7 +57,7 @@ public static (List SpectralEvents, List DecibelPlots) GetCom spectralEvents.AddRange(oscillationEvents); // prepare plot of resultant Harmonics decibel array. - var plot = Plot.PreparePlot(scores, $"{profileName} (Oscillations:{threshold:d0}db)", threshold.Value); + var plot = Plot.PreparePlot(scores, $"{profileName} (Oscillations:{threshold:F0}db)", threshold.Value); plots.Add(plot); } diff --git a/src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs b/src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs index 5f7a664ce..a90830faa 100644 --- a/src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs +++ b/src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs @@ -44,7 +44,7 @@ public static (List Events, List DecibelPlots) GetForwardTrac spectralEvents.AddRange(events); - var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Chirps:{threshold.Value:d0}dB)", threshold.Value); + var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Chirps:{threshold.Value:F0}dB)", threshold.Value); plots.Add(plot); } @@ -73,6 +73,10 @@ public static (List Events, double[] CombinedIntensity) GetForwardT double minDuration = parameters.MinDuration.Value; double maxDuration = parameters.MaxDuration.Value; + // Calculate the max score for normalisation purposes + var maxScore = decibelThreshold * 5; + var scoreRange = new Interval(0, maxScore); + var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, @@ -105,7 +109,7 @@ public static (List Events, double[] CombinedIntensity) GetForwardT // Initialise each track as an event and store it in a list of acoustic events var events = new List(); - // Also get the combined decibel array. + // Also get the combined decibel intensity array. var combinedIntensityArray = new double[frameCount]; // The following lines are used only for debug purposes. @@ -131,13 +135,11 @@ public static (List Events, double[] CombinedIntensity) GetForwardT //Following line used only for debug purposes. Can save as image. //spectrogram.Mutate(x => track.Draw(x, options)); - var maxScore = decibelThreshold * 5; - var scoreRange = new Interval(0, maxScore); var ae = new ChirpEvent(track, scoreRange) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, - Name = "noName", + Name = "Chirp", }; events.Add(ae); @@ -189,7 +191,7 @@ public static List GetForwardTracks(double[,] peaks, double minDuration, // Visit each spectral peak in order. Each may be start of possible track var track = GetForwardTrack(peaks, row, col, threshold, converter); - // a forward track should have length >2 + // a track should have length > 2 if (track.PointCount > 2) { tracks.Add(track); diff --git a/src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs b/src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs index 567df6eb7..a5e2e0c10 100644 --- a/src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs +++ b/src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs @@ -40,7 +40,7 @@ public static (List Events, List DecibelPlots) GetOnebinTrack spectralEvents.AddRange(events); - var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Whistle:{threshold.Value:d0}dB)", threshold.Value); + var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Whistles:{threshold.Value:F0}dB)", threshold.Value); plots.Add(plot); } @@ -114,6 +114,22 @@ public static (List ListOfevents, double[] CombinedIntensityArray) foreach (var track in tracks) { + // fill the intensity array with decibel values + 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]); + } + + // Skip tracks that do not have duration within required duration bounds. + if (track.DurationSeconds < minDuration || track.DurationSeconds > maxDuration) + { + continue; + } + + //Following line used only for debug purposes. Can save as image. + //spectrogram.Mutate(x => track.Draw(x, options)); var ae = new WhistleEvent(track, scoreRange) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, @@ -122,14 +138,6 @@ public static (List ListOfevents, double[] CombinedIntensityArray) }; 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. @@ -171,8 +179,8 @@ public static List GetOnebinTracks(double[,] peaks, double minDuration, d // Visit each spectral peak in order. Each may be start of possible whistle track var track = GetOnebinTrack(peaks, row, col, threshold, converter); - //If track has length within duration bounds, then add the track to list. - if (track.DurationSeconds >= minDuration && track.DurationSeconds <= maxDuration) + // a track should have length > 2 + if (track.PointCount > 2) { tracks.Add(track); } diff --git a/src/AudioAnalysisTools/Tracks/OneframeTrackAlgorithm.cs b/src/AudioAnalysisTools/Tracks/OneframeTrackAlgorithm.cs index 196e11b3b..ce16c9cfc 100644 --- a/src/AudioAnalysisTools/Tracks/OneframeTrackAlgorithm.cs +++ b/src/AudioAnalysisTools/Tracks/OneframeTrackAlgorithm.cs @@ -6,6 +6,7 @@ namespace AudioAnalysisTools.Tracks { using System; using System.Collections.Generic; + using System.Linq; using AnalysisPrograms.Recognizers.Base; using AudioAnalysisTools.Events; using AudioAnalysisTools.Events.Tracks; @@ -38,7 +39,7 @@ public static (List Events, List DecibelPlots) GetOneFrameTra spectralEvents.AddRange(events); - var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Clicks:{threshold.Value:d0}dB)", threshold.Value); + var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Clicks:{threshold.Value:F0}dB)", threshold.Value); plots.Add(plot); } @@ -68,6 +69,9 @@ public static (List Events, double[] Intensity) GetOneFrameTracks( var minBandwidthHertz = parameters.MinBandwidthHertz.Value; var maxBandwidthHertz = parameters.MaxBandwidthHertz.Value; + // Calculate the max score for normalisation purposes + var maxScore = decibelThreshold * 5; + var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, @@ -100,34 +104,46 @@ public static (List Events, double[] Intensity) GetOneFrameTracks( } } - //NOTE: the Peaks matrix is same size as the sonogram. + // Get a list of tracks + // NOTE: the Peaks matrix is same size as the sonogram. var tracks = GetOneFrameTracks(peaks, minBin, maxBin, minBandwidthHertz, maxBandwidthHertz, decibelThreshold, converter); - // initialise tracks as events and get the combined intensity array. - var events = new List(); + // Initialise each track as an event and store it in a list of acoustic events + var events = new List(); + + // Also get the combined decibel intensity array. var temporalIntensityArray = new double[frameCount]; - var maxScore = decibelThreshold * 5; + + // Initialise events with tracks. foreach (var track in tracks) { + // fill the intensity array with decibel values + var startRow = converter.FrameFromStartTime(track.StartTimeSeconds); + var amplitudeTrack = track.GetAmplitudeOverTimeFrames(); + for (int i = 0; i < amplitudeTrack.Length; i++) + { + temporalIntensityArray[startRow + i] += amplitudeTrack[i]; + } + + // Skip tracks that do not have duration within required duration bounds. + if (track.TrackBandWidthHertz < minBandwidthHertz || track.TrackBandWidthHertz > maxBandwidthHertz) + { + continue; + } + + //Following line used only for debug purposes. Can save as image. + //spectrogram.Mutate(x => track.Draw(x, options)); var ae = new ClickEvent(track, maxScore) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, - Name = "noName", + Name = "Click", }; 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++) - { - temporalIntensityArray[startRow + i] += amplitudeTrack[i]; - } } - // MAY NOT WANT TO Do THIS FOR ONE-FRAME tracks + // MAY NOT WANT TO Do THIS FOR ONE-FRAME tracks (clicks) // combine proximal events that occupy similar frequency band //if (combineProximalSimilarEvents) //{ @@ -137,7 +153,8 @@ public static (List Events, double[] Intensity) GetOneFrameTracks( // //events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference); //} - return (events, temporalIntensityArray); + List returnEvents = events.Cast().ToList(); + return (returnEvents, temporalIntensityArray); } /// @@ -172,8 +189,8 @@ public static List GetOneFrameTracks(double[,] peaks, int minBin, int max // Visit each spectral peak in order. Each may be start of possible track var track = GetOneFrameTrack(peaks, row, col, maxBin, threshold, converter); - //If track lies within the correct bandWidth range, then return as track. - if (track.TrackBandWidthHertz >= minBandwidthHertz && track.TrackBandWidthHertz <= maxBandwidthHertz) + // a track should have length > 2 + if (track.PointCount > 2) { tracks.Add(track); } diff --git a/src/AudioAnalysisTools/Tracks/UpwardTrackAlgorithm.cs b/src/AudioAnalysisTools/Tracks/UpwardTrackAlgorithm.cs index e2b938794..449b8b967 100644 --- a/src/AudioAnalysisTools/Tracks/UpwardTrackAlgorithm.cs +++ b/src/AudioAnalysisTools/Tracks/UpwardTrackAlgorithm.cs @@ -46,7 +46,7 @@ public static (List Events, List DecibelPlots) GetUpwardTrack spectralEvents.AddRange(events); - var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Clicks:{threshold.Value:d0}dB)", threshold.Value); + var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Whips:{threshold.Value:F0}dB)", threshold.Value); plots.Add(plot); } @@ -77,6 +77,10 @@ public static (List Events, double[] CombinedIntensity) GetUpwardTr var minBandwidthHertz = parameters.MinBandwidthHertz.Value; var maxBandwidthHertz = parameters.MaxBandwidthHertz.Value; + // Calculate the max score for normalisation purposes + var maxScore = decibelThreshold * 5; + var scoreRange = new Interval(0, maxScore); + var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, @@ -110,19 +114,9 @@ public static (List Events, double[] CombinedIntensity) GetUpwardTr // initialise tracks as events and get the combined intensity array. var events = new List(); var temporalIntensityArray = new double[frameCount]; - var scoreRange = new Interval(0.0, decibelThreshold * 5); foreach (var track in tracks) { - var ae = new WhipEvent(track, scoreRange) - { - SegmentStartSeconds = segmentStartOffset.TotalSeconds, - SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, - Name = "Whip", - }; - - events.Add(ae); - // fill the intensity array //var startRow = (int)converter.TemporalScale.To(track.StartTimeSeconds); var startRow = converter.FrameFromStartTime(track.StartTimeSeconds); @@ -131,6 +125,23 @@ public static (List Events, double[] CombinedIntensity) GetUpwardTr { temporalIntensityArray[startRow + i] += amplitudeTrack[i]; } + + //Skip tracks that do not sit within the correct bandWidth range. + if (track.TrackBandWidthHertz < minBandwidthHertz || track.TrackBandWidthHertz > maxBandwidthHertz) + { + continue; + } + + //Following line used only for debug purposes. Can save as image. + //spectrogram.Mutate(x => track.Draw(x, options)); + var ae = new WhipEvent(track, scoreRange) + { + SegmentStartSeconds = segmentStartOffset.TotalSeconds, + SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, + Name = "Whip", + }; + + events.Add(ae); } List returnEvents = events.Cast().ToList(); @@ -164,8 +175,8 @@ public static List GetUpwardTracks(double[,] peaks, int minBin, int maxBi // Visit each spectral peak in order. Each may be start of possible track var track = GetUpwardTrack(peaks, row, col, maxBin, threshold, converter); - //If track lies within the correct bandWidth range, then return as track. - if (track.TrackBandWidthHertz >= minBandwidthHertz && track.TrackBandWidthHertz <= maxBandwidthHertz) + // a track should have length > 2 + if (track.PointCount > 2) { tracks.Add(track); }