diff --git a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml index 72dc1a11f..44639e4fb 100644 --- a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml +++ b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml @@ -33,7 +33,7 @@ Profiles: MinHz: 200 MaxHz: 2000 # duration of DCT in seconds - DctDuration: 1.0 + DctDuration: 0.8 # minimum acceptable value of a DCT coefficient DctThreshold: 0.5 # ignore oscillation rates below the min & above the max threshold @@ -46,7 +46,7 @@ Profiles: MinDuration: 1.0 MaxDuration: 10.0 # Event threshold - use this to determine FP / FN trade-off for events. - EventThreshold: 0.50 + EventThreshold: 0.60 #Agonist: # This notation means the Groote profile has all of the settings that the Standard profile has, # however, the MinHz and MaxHz properties have been overridden. diff --git a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs index 882a0fef1..b35767cd7 100644 --- a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs +++ b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs @@ -3,7 +3,7 @@ // All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). // // -// This is a template recognizer for the Australian Flying Fox. +// This is a recognizer for the Australian Flying Fox. // Since there are several species, this project is started using only the generic name for Flying Foxes. // Proposed algorithm has 8 steps @@ -74,7 +74,7 @@ public override void SummariseResults( /// Do your analysis. This method is called once per segment (typically one-minute segments). /// /// one minute of audio recording. - /// config file. + /// config file that contains parameters used by all profiles. /// when recording starts. /// not sure what this is. /// where the recogniser results can be found. @@ -123,18 +123,6 @@ public override RecognizerResults Recognize(AudioRecording audioRecording, Confi log.Warn("Could not access Wingbeats configuration parameters"); } - //RecognizerResults combinedResults = null; - - //// combine the results - //return new RecognizerResults() - //{ - // Events = results1.Events.AddRange(results2.Events), - // Hits = null, - // ScoreTrack = null, - // Plots = plots, - // Sonogram = sonogram, - //}; - // combine the results if (results1 != null && results2 != null) { @@ -346,8 +334,6 @@ internal static RecognizerResults WingBeats(AudioRecording audioRecording, Confi double dctThreshold = profile.GetDoubleOrNull("DctThreshold") ?? 0.5; double minOscilFreq = profile.GetDoubleOrNull("MinOscilFreq") ?? 4.0; double maxOscilFreq = profile.GetDoubleOrNull("MaxOscilFreq") ?? 6.0; - //var minTimeSpan = TimeSpan.FromSeconds(minDurationSeconds); - //var maxTimeSpan = TimeSpan.FromSeconds(maxDurationSeconds); double eventThreshold = profile.GetDoubleOrNull("EventThreshold") ?? 0.3; //###################### @@ -385,6 +371,32 @@ internal static RecognizerResults WingBeats(AudioRecording audioRecording, Confi out var hits, segmentStartOffset); + /* + // Look for wing beats using pulse train detector + double pulsesPerSecond = 5.1; + var scores = PulseTrain.GetPulseTrainScore(decibelArray, pulsesPerSecond, sonogram.FramesPerSecond, 1.0); + + //iii: CONVERT Pulse Train SCORES TO ACOUSTIC EVENTS + double pulseTrainThreshold = 0.5; + var minTimeSpan = TimeSpan.FromSeconds(minDurationSeconds); + var maxTimeSpan = TimeSpan.FromSeconds(maxDurationSeconds); + var acousticEvents = AcousticEvent.GetEventsAroundMaxima( + scores, + segmentStartOffset, + minHz, + maxHz, + pulseTrainThreshold, + minTimeSpan, + maxTimeSpan, + sonogram.FramesPerSecond, + sonogram.FBinWidth + ); + + double scoreThreshold = 0.5; + var normalisedScoreArray = DataTools.NormaliseInZeroOne(scores, 0, 1.0); + var plot2 = new Plot(speciesName + " Wingbeat Pulse-train Score", normalisedScoreArray, scoreThreshold); + */ + // prepare plots double decibelThreshold = 12.0; double intensityNormalisationMax = 3 * decibelThreshold; diff --git a/src/TowseyLibrary/PulseTrain.cs b/src/TowseyLibrary/PulseTrain.cs new file mode 100644 index 000000000..1579da573 --- /dev/null +++ b/src/TowseyLibrary/PulseTrain.cs @@ -0,0 +1,161 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). +// +// +// This class contains methods to recognise pulse trains. +// It is an alternative to using the Oscillations class. + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TowseyLibrary +{ + using Acoustics.Shared.ConfigFile; + using MathNet.Numerics.LinearAlgebra.Solvers; + + public static class PulseTrain + { + /// + /// This method creates a template to recognise two pulses that are possibly part of a pulse train. + /// The template is designed to detect pulse trains of at least 2 pulses! + /// The template is bounded either end by silence and then a pulse. i.e. .:|:. ... .:|:. where .=zero or a negative residual value, := 0.5 and |= 1.0. + /// Any number of residual values may separate the pulses at either end. In this method, templates are created with 6 non-zero values and the remainder are negative. + /// The sum of the positive values = 4.0. + /// The sum of the values in the template should = zero. + /// Designed this way, the minimum pulse length is about 4 or 5 and the minimum template length is about 10; + /// + /// length or number of frames between two pulses. + /// the template. + public static double[] GetPulseTrainTemplate(int pulseLength) + { + int templateLength = pulseLength + 5; + double residual = 4 / (double)(templateLength - 6); + + var template = new double[templateLength]; + template[0] = -residual; + template[1] = 0.5; + template[2] = 1.0; + template[3] = 0.5; + for (int i = 4; i < templateLength - 4; i++) + { + template[i] = -residual; + } + + template[templateLength - 4] = 0.5; + template[templateLength - 3] = 1.0; + template[templateLength - 2] = 0.5; + template[templateLength - 1] = -residual; + template = DataTools.normalise2UnitLength(template); + return template; + } + + /// + /// Only three pulses included in the single template output by this method. + /// Will generalise if it seems worthwhile. + /// + public static double[] GetPulseTrainTemplate(int pulseLength, int pulseCount) + { + int templateLength = (pulseLength * pulseCount) + 5; + var template = new double[templateLength]; + int templateHalfLength = templateLength / 2; + + for (int i = 0; i < templateLength; i++) + { + template[i] = -1.0; + } + + template[1] = 0.3; + template[2] = 1.0; + template[3] = 0.3; + + template[templateHalfLength - 1] = 0.3; + template[templateHalfLength] = 1.0; + template[templateHalfLength + 1] = 0.3; + + template[templateLength - 4] = 0.3; + template[templateLength - 3] = 1.0; + template[templateLength - 2] = 0.3; + //template = DataTools.normalise2UnitLength(template); + return template; + } + + /// + /// returns the length of a pulse interval in frames given pulses and frame rates in seconds. + /// + /// number of pulses per second. + /// frames per second - i.e. assuming the application is applied to a sequence of spectral frames. + /// the template. + public static double[] GetPulseTrainTemplate(double pulsesPerSecond, double framesPerSecond) + { + int frameCount = (int)Math.Round(framesPerSecond / pulsesPerSecond); + return GetPulseTrainTemplate(frameCount); + } + + public static double[] GetPulseTrainScore(double[] signal, double pulsesPerSecond, double framesPerSecond, double thresholdValue) + { + int pulseCount = 2; + int frameCount = (int)Math.Round(framesPerSecond / pulsesPerSecond); + var templates = new List + { + GetPulseTrainTemplate(frameCount, pulseCount), + GetPulseTrainTemplate(frameCount - 1, pulseCount), + GetPulseTrainTemplate(frameCount + 1, pulseCount), + }; + int signalLength = signal.Length; + + var scores = new double[signalLength]; + + for (int i = 2; i < signalLength - templates[2].Length; i++) + { + // skip if value is below threshold + if (signal[i] < thresholdValue) + { + continue; + } + + // skip if value is not maximum + if (signal[i] < signal[i - 1] || signal[i] < signal[i + 1]) + { + continue; + } + + // get Cosine similarity for each of three templates. + var templateScores = new double[3]; + + // get the local nh of signal for template 0 and get score + var nh = DataTools.Subarray(signal, i, templates[0].Length); + nh = DataTools.normalise2UnitLength(nh); + templateScores[0] = DataTools.DotProduct(nh, templates[0]); + + // get the local nh of signal for template 1 + nh = DataTools.Subarray(signal, i, templates[1].Length); + nh = DataTools.normalise2UnitLength(nh); + templateScores[1] = DataTools.DotProduct(nh, templates[1]); + + // get the local nh of signal for template 2 + nh = DataTools.Subarray(signal, i, templates[2].Length); + nh = DataTools.normalise2UnitLength(nh); + templateScores[2] = DataTools.DotProduct(nh, templates[2]); + + double maxScore = templateScores.Max(); + if (maxScore > 0.0) + { + for (int j = 0; j < templates[0].Length - 1; j++) + { + if (maxScore > scores[i + j]) + { + scores[i + j] = maxScore; + } + } + } + } + + return scores; + } + } +} diff --git a/src/TowseyLibrary/TowseyLibrary.csproj b/src/TowseyLibrary/TowseyLibrary.csproj index 04269c212..42fabab8d 100644 --- a/src/TowseyLibrary/TowseyLibrary.csproj +++ b/src/TowseyLibrary/TowseyLibrary.csproj @@ -140,6 +140,7 @@ +