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 @@
+