Skip to content

Commit

Permalink
Write methods that detect acoustic activity in band above whistle
Browse files Browse the repository at this point in the history
Issue #297 This is way of filtering out false positive whistles.
  • Loading branch information
towsey committed May 7, 2020
1 parent 5fa7cfb commit 04c95d5
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 33 deletions.
21 changes: 11 additions & 10 deletions src/AudioAnalysisTools/Events/Types/WhistleEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,12 @@ public static List<EventCommon> CombineAdjacentWhistleEvents(List<WhistleEvent>
return events.Cast<EventCommon>().ToList();
}

//var whistleEvents = new List<EventCommon>();
for (int i = events.Count - 1; i >= 0; i--)
{
for (int j = i - 1; j >= 0; j--)
{
var a = events[i] as WhistleEvent;
var b = events[j] as WhistleEvent;
var a = events[i];
var b = events[j];

bool eventsOverlapInTime = CompositeEvent.EventsOverlapInTime(a, b);
bool eventsAreInSimilarFreqBand = Math.Abs(a.LowFrequencyHertz - b.HighFrequencyHertz) < hertzDifference || Math.Abs(a.HighFrequencyHertz - b.LowFrequencyHertz) < hertzDifference;
Expand All @@ -95,11 +94,14 @@ public static List<EventCommon> CombineAdjacentWhistleEvents(List<WhistleEvent>
}

/// <summary>
/// Merges two whistle events into one event.
/// Merges two whistle events into one whistle event.
/// This is useful because a typical bird whistle contains side bands and therefore covers more than one frequency bin.
/// The Whistle detection algorithm detects whistle content in the side bins but puts each bin content in a different event.
/// THis method merges events that belong to the same whistle call.
/// </summary>
/// <param name="e1">first event.</param>
/// <param name="e2">second event.</param>
/// <returns>a composite event.</returns>
/// <returns>a new whistle event .</returns>
public static WhistleEvent MergeTwoWhistleEvents(WhistleEvent e1, WhistleEvent e2)
{
// Assume that we only merge events that are in the same recording segment.
Expand All @@ -117,17 +119,16 @@ public static WhistleEvent MergeTwoWhistleEvents(WhistleEvent e1, WhistleEvent e
Name = e1.Name,
EventEndSeconds = Math.Max(e1.EventEndSeconds, e2.EventEndSeconds),
EventStartSeconds = Math.Min(e1.EventStartSeconds, e2.EventStartSeconds),
HighFrequencyHertz = Math.Max(e1.EventEndSeconds, e2.EventEndSeconds),
LowFrequencyHertz = Math.Max(e1.EventEndSeconds, e2.EventEndSeconds),
Score = e1.Score,
HighFrequencyHertz = Math.Max(e1.HighFrequencyHertz, e2.HighFrequencyHertz),
LowFrequencyHertz = Math.Min(e1.LowFrequencyHertz, e2.LowFrequencyHertz),
Score = Math.Max(e1.Score, e2.Score),
ScoreRange = e1.ScoreRange,
SegmentDurationSeconds = e1.SegmentDurationSeconds,
SegmentStartSeconds = e1.SegmentStartSeconds,
FileName = e1.FileName,
};

newEvent.ResultStartSeconds = e1.EventStartSeconds;

newEvent.ResultStartSeconds = newEvent.EventStartSeconds;
return newEvent;
}
}
Expand Down
63 changes: 40 additions & 23 deletions src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,27 +83,6 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)
var maxScore = decibelThreshold * 5;
foreach (var track in tracks)
{
// Now check the buffer zone above the whistle.
// It should not contain high acoustic content.
var bufferHertz = 300;
var bufferBins = (int)Math.Round(bufferHertz / binWidth);

var bottomBufferBin = converter.GetFreqBinFromHertz(track.HighFreqHertz) + 5;
var topBufferBin = bottomBufferBin + bufferBins;
var frameStart = converter.FrameFromStartTime(track.StartTimeSeconds);
var frameEnd = converter.FrameFromStartTime(track.EndTimeSeconds);
var subMatrix = MatrixTools.Submatrix<double>(sonogramData, frameStart, bottomBufferBin, frameEnd, topBufferBin);
var averageRowDecibels = MatrixTools.GetRowAverages(subMatrix);
var av = averageRowDecibels.Average();

Console.WriteLine($"###################################Buffer Average decibels = {av}");
if (av > decibelThreshold)
{
// There is too much acoustic activity in the buffer zone.
// This is unlikely to be a whistle.
continue;
}

var ae = new WhistleEvent(track, maxScore)
{
SegmentStartSeconds = segmentStartOffset.TotalSeconds,
Expand All @@ -124,10 +103,27 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)

// This algorithm tends to produce temporally overlapped whistle events in adjacent channels.
// Combine overlapping whistle events
var hertzDifference = 2 * binWidth;
var hertzDifference = 4 * binWidth;
var whistleEvents = WhistleEvent.CombineAdjacentWhistleEvents(events, hertzDifference);

return (whistleEvents, combinedIntensityArray);
// Finally filter the whistles for presense of excess noise in buffer band just above the whistle.
// Excess noise would suggest this is not a whistle event.
var bufferHertz = 300;
var bufferBins = (int)Math.Round(bufferHertz / binWidth);
var filteredEvents = new List<EventCommon>();
foreach (var ev in whistleEvents)
{
var avNhAmplitude = GetAverageAmplitudeInNeighbourhood((SpectralEvent)ev, sonogramData, bufferBins, converter);
Console.WriteLine($"###################################Buffer Average decibels = {avNhAmplitude}");

if (avNhAmplitude < decibelThreshold)
{
// There is little acoustic activity in the buffer zone above the whistle. It is likely to be a whistle.
filteredEvents.Add(ev);
}
}

return (filteredEvents, combinedIntensityArray);
}

/// <summary>
Expand Down Expand Up @@ -211,5 +207,26 @@ public static Track GetOnebinTrack(double[,] peaks, int startRow, int bin, doubl

return track;
}

/// <summary>
/// Calculates the average amplitude in the frequency just above the whistle.
/// If it contains above threshold acoustic content, this is unlikely to be a whistle.
/// </summary>
/// <param name="ev">The event.</param>
/// <param name="sonogramData">The spectrogram data as matrix with origin top/left.</param>
/// <param name="bufferBins">THe badnwidth of the buffer zone in bins.</param>
/// <param name="converter">A converter to convert seconds/Hertz to frames/bins.</param>
/// <returns>Average of the spectrogram amplitude in buffer band above whistler.</returns>
public static double GetAverageAmplitudeInNeighbourhood(SpectralEvent ev, double[,] sonogramData, int bufferBins, UnitConverters converter)
{
var bottomBufferBin = converter.GetFreqBinFromHertz(ev.HighFrequencyHertz) + 5;
var topBufferBin = bottomBufferBin + bufferBins;
var frameStart = converter.FrameFromStartTime(ev.EventStartSeconds);
var frameEnd = converter.FrameFromStartTime(ev.EventEndSeconds);
var subMatrix = MatrixTools.Submatrix<double>(sonogramData, frameStart, bottomBufferBin, frameEnd, topBufferBin);
var averageRowDecibels = MatrixTools.GetRowAverages(subMatrix);
var av = averageRowDecibels.Average();
return av;
}
}
}

0 comments on commit 04c95d5

Please sign in to comment.