diff --git a/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs b/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs
index c95e87d34..77fd1fcba 100644
--- a/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs
+++ b/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs
@@ -129,12 +129,135 @@ public override RecognizerResults Recognize(
combinedResults.NewEvents = EventExtentions.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold);
PipitLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}");
+ combinedResults.NewEvents = FilterEventsOnFrequencyProfile(combinedResults.NewEvents);
+
+ //foreach (var ev in whistleEvents)
+ //{
+ // // Calculate frequency profile score for event
+ // SetFrequencyProfileScore((WhistleEvent)ev);
+ //}
+
//UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots.
// NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in UserName.SpeciesName.yml config file.
//GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName);
return combinedResults;
}
+ ///
+ /// This method assumes that the only events of interest are composite events.
+ ///
+ /// THe current list of events.
+ /// A list of composite events.
+ public static List FilterEventsOnFrequencyProfile(List events)
+ {
+ // select only the composite events.
+ //var compositeEvents = events.Select(x => (CompositeEvent)x).ToList();
+ var (compositeEvents, others) = events.FilterForEventType();
+
+ if (compositeEvents == null)
+ {
+ return null;
+ }
+
+ // get the composite track for each composite event.
+ var returnEvents = new List();
+ foreach (var ev in compositeEvents)
+ {
+ var componentEvents = ev.ComponentEvents;
+ var points = EventExtentions.GetCompositeTrack(componentEvents).ToArray();
+ var length = points.Length - 1;
+
+ //WriteFrequencyProfile(points);
+
+ // Only select events having strong downward slope in spectrogram.
+ var avFirstTwoEvents = (points[0].Hertz.Minimum + points[0].Hertz.Minimum) / 2;
+ var avLastTwoEvents = (points[length - 1].Hertz.Minimum + points[length - 2].Hertz.Minimum) / 2;
+ if (avFirstTwoEvents - avLastTwoEvents > 500)
+ {
+ returnEvents.Add(ev);
+ }
+ }
+
+ return returnEvents;
+ }
+
+ ///
+ /// The Boobook call syllable is shaped like an inverted "U". Its total duration is close to 0.15 seconds.
+ /// The rising portion lasts for 0.06s, followed by a turning portion, 0.03s, followed by the decending portion of 0.06s.
+ /// The constants for this method were obtained from the calls in a Gympie recording obtained by Yvonne Phillips.
+ ///
+ /// An event containing at least one forward track i.e. a chirp.
+ public static void SetFrequencyProfileScore(ChirpEvent ev)
+ {
+ const double risingDuration = 0.06;
+ const double gapDuration = 0.03;
+ const double fallingDuration = 0.06;
+
+ var track = ev.Tracks.First();
+ var profile = track.GetTrackFrequencyProfile().ToArray();
+
+ // get the first point
+ var firstPoint = track.Points.First();
+ var frameDuration = firstPoint.Seconds.Maximum - firstPoint.Seconds.Minimum;
+ var risingFrameCount = (int)Math.Floor(risingDuration / frameDuration);
+ var gapFrameCount = (int)Math.Floor(gapDuration / frameDuration);
+ var fallingFrameCount = (int)Math.Floor(fallingDuration / frameDuration);
+
+ var startSum = 0.0;
+ if (profile.Length >= risingFrameCount)
+ {
+ for (var i = 0; i <= risingFrameCount; i++)
+ {
+ startSum += profile[i];
+ }
+ }
+
+ int startFrame = risingFrameCount + gapFrameCount;
+ int endFrame = startFrame + fallingFrameCount;
+ var endSum = 0.0;
+ if (profile.Length >= endFrame)
+ {
+ for (var i = startFrame; i <= endFrame; i++)
+ {
+ endSum += profile[i];
+ }
+ }
+
+ // set score to 1.0 if the profile has inverted U shape.
+ double score = 0.0;
+ if (startSum > 0.0 && endSum < 0.0)
+ {
+ score = 1.0;
+ }
+
+ ev.FrequencyProfileScore = score;
+ }
+
+ ///
+ /// .
+ ///
+ /// List of spectral points.
+ public static void WriteFrequencyProfile(ISpectralPoint[] points)
+ {
+ /* Here are the frequency profiles of some events.
+ * Note that the first five frames (0.057 seconds) have positive slope and subsequent frames have negative slope.
+ * The final frames are likely to be echo and to be avoided.
+ * Therefore take the first 0.6s to calculate the positive slope, leave a gap of 0.025 seconds and then get negative slope from the next 0.6 seconds.
+ */
+
+ if (points != null)
+ {
+ var str = $"Track({points[0].Seconds.Minimum:F2}):";
+
+ foreach (var point in points)
+ {
+ str += $" {point.Hertz.Minimum},";
+ }
+
+ Console.WriteLine(str);
+ }
+ }
+
/*
///
/// Summarize your results. This method is invoked exactly once per original file.
diff --git a/src/AnalysisPrograms/Recognizers/Birds/BotaurusPoiciloptilus.cs b/src/AnalysisPrograms/Recognizers/Birds/BotaurusPoiciloptilus.cs
index c96e8342f..1fb4d96d3 100644
--- a/src/AnalysisPrograms/Recognizers/Birds/BotaurusPoiciloptilus.cs
+++ b/src/AnalysisPrograms/Recognizers/Birds/BotaurusPoiciloptilus.cs
@@ -93,15 +93,6 @@ public override RecognizerResults Recognize(
//var newEvents = spectralEvents.Cast().ToList();
//var spectralEvents = events.Select(x => (SpectralEvent)x).ToList();
- // Uncomment the next line when want to obtain the event frequency profiles.
- // WriteFrequencyProfiles(chirpEvents);
-
- //foreach (var ev in whistleEvents)
- //{
- // // Calculate frequency profile score for event
- // SetFrequencyProfileScore((WhistleEvent)ev);
- //}
-
if (combinedResults.NewEvents.Count == 0)
{
BitternLog.Debug($"Return zero events.");
@@ -185,6 +176,15 @@ public override RecognizerResults Recognize(
combinedResults.NewEvents = EventExtentions.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold);
BitternLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}");
+ // Uncomment the next line when want to obtain the event frequency profiles.
+ // WriteFrequencyProfiles(chirpEvents);
+
+ //foreach (var ev in whistleEvents)
+ //{
+ // // Calculate frequency profile score for event
+ // SetFrequencyProfileScore((WhistleEvent)ev);
+ //}
+
//UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots.
// NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in UserName.SpeciesName.yml config file.
//GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName);
diff --git a/src/AudioAnalysisTools/Events/EventExtentions.cs b/src/AudioAnalysisTools/Events/EventExtentions.cs
index 3ac65c5f2..5d6f63a25 100644
--- a/src/AudioAnalysisTools/Events/EventExtentions.cs
+++ b/src/AudioAnalysisTools/Events/EventExtentions.cs
@@ -9,6 +9,7 @@ namespace AudioAnalysisTools.Events
using System.Linq;
using AudioAnalysisTools.Events.Types;
using AudioAnalysisTools.StandardSpectrograms;
+ using MoreLinq;
using TowseyLibrary;
public static class EventExtentions
@@ -360,5 +361,26 @@ public static List FilterEventsOnCompositeContent(
return filteredEvents;
}
+
+ ///
+ /// Combines all the tracks in all the events in the passed list into a single track.
+ /// Each frame in the composite event is assigned the spectral point having maximum amplitude.
+ /// The points in the returned array are in temporal order.
+ ///
+ /// List of spectral events.
+ public static IEnumerable GetCompositeTrack(List events)
+ {
+ var spectralEvents = events.Select(x => (WhipEvent)x);
+ var points = spectralEvents.SelectMany(x => x.Tracks.SelectMany(t => t.Points));
+
+ // group all the points by their start time.
+ var groupStarts = points.GroupBy(p => p.Seconds);
+
+ // for each group, for each point in group, choose the point having maximum (amplitude) value.
+ // Since there maybe multiple points having maximum amplitude, we pick the first one.
+ var maxAmplitudePoints = groupStarts.Select(g => g.MaxBy(p => p.Value).First());
+
+ return maxAmplitudePoints.OrderBy(p => p);
+ }
}
}