Skip to content

Commit

Permalink
Create two new generic post-processing steps
Browse files Browse the repository at this point in the history
Issue #370: Shift event bandwidth filter and component count filter to Generic class.
Rework filter for acoustic activity in neighbourhood - i.e. event sidebands.
  • Loading branch information
towsey authored and atruskie committed Oct 14, 2020
1 parent 6ec55c6 commit 2ee86d8
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 18 deletions.
39 changes: 34 additions & 5 deletions src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,20 @@ public override RecognizerResults Recognize(
var startDiff = configuration.SyllableStartDifference;
var hertzDiff = configuration.SyllableHertzGap;
allResults.NewEvents = CompositeEvent.CombineProximalEvents(spectralEvents1, TimeSpan.FromSeconds(startDiff), (int)hertzDiff);
Log.Debug($"Event count after combining proximals = {allResults.NewEvents.Count}");
Log.Debug($"Event count after combining proximal events = {allResults.NewEvents.Count}");

// Now filter on COMPONENT COUNT in Composite events.
int maxComponentCount = configuration.SyllableMaxCount;
allResults.NewEvents = EventExtentions.FilterEventsOnCompositeContent(allResults.NewEvents, maxComponentCount);
Log.Debug($"Event count after filtering on component count = {allResults.NewEvents.Count}");
}

// 1: Filter the events for bandwidth in Hertz
var expectedEventBandwidth = configuration.ExpectedBandwidth;
var sd = configuration.BandwidthStandardDeviation;
allResults.NewEvents = EventExtentions.FilterOnBandwidth(allResults.NewEvents, expectedEventBandwidth, sd, sigmaThreshold: 3.0);
Log.Debug($"Event count after filtering on bandwidth = {allResults.NewEvents.Count}");

// 3: Filter events on the amount of acoustic activity in their upper and lower neighbourhoods - their buffer zone.
// The idea is that an unambiguous event should have some acoustic space above and below.
// The filter requires that the average acoustic activity in each frame and bin of the upper and lower buffer zones should not exceed the user specified decibel threshold.
Expand All @@ -372,9 +383,9 @@ public override RecognizerResults Recognize(
configuration.NeighbourhoodLowerHertzBuffer,
configuration.NeighbourhoodUpperHertzBuffer,
segmentStartOffset,
configuration.NeighbourhoodDbThreshold);
configuration.NeighbourhoodDecibelBuffer);

Log.Debug($"Event count after filtering on neighbourhood = {allResults.NewEvents.Count}");
Log.Debug($"Event count after filtering on acoustic activity in upper/lower neighbourhood = {allResults.NewEvents.Count}");
}

return allResults;
Expand Down Expand Up @@ -473,6 +484,23 @@ public class GenericRecognizerConfig : RecognizerConfig, INamedProfiles<object>
/// </summary>
public double SyllableHertzGap { get; set; }

/// <summary>
/// Gets or sets a value indicating the maximum allowable number of syllables in a syllable sequence.
/// </summary>
public int SyllableMaxCount { get; set; }

// #### The next two properties determine filtering of events based on their bandwidth

/// <summary>
/// Gets or sets a value indicating the Expected bandwidth of an event.
/// </summary>
public int ExpectedBandwidth { get; set; }

/// <summary>
/// Gets or sets a value indicating the standard deviation of the expected bandwidth.
/// </summary>
public int BandwidthStandardDeviation { get; set; }

// #### The next three properties determine filtering of events based on acoustic conctent of upper and lower buffer zones.

/// <summary>
Expand All @@ -488,10 +516,11 @@ public class GenericRecognizerConfig : RecognizerConfig, INamedProfiles<object>
public int NeighbourhoodLowerHertzBuffer { get; set; }

/// <summary>
/// Gets or sets a value indicating the decibel threshold for acoustic activity in the upper and lower buffer zones.
/// Gets or sets a value indicating the decibel gap/difference between acoustic activity in the event and in the upper/lower buffer zones.
/// BufferAcousticActivity must be LessThan (EventAcousticActivity - NeighbourhoodDecibelBuffer)
/// This value is used only if NeighbourhoodLowerHertzBuffer > 0 OR NeighbourhoodUpperHertzBuffer > 0.
/// </summary>
public double NeighbourhoodDbThreshold { get; set; }
public double NeighbourhoodDecibelBuffer { get; set; }
}
}
}
62 changes: 49 additions & 13 deletions src/AudioAnalysisTools/Events/EventExtentions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,14 @@ public static (List<T> TargetEvents, List<U> OtherEvents) FilterForEventType<T,
public static List<EventCommon> FilterOnBandwidth(List<EventCommon> events, double average, double sd, double sigmaThreshold)
{
var minBandwidth = average - (sd * sigmaThreshold);
if (minBandwidth < 0.0)
{
throw new Exception("Invalid bandwidth passed to method EventExtentions.FilterOnBandwidth().");
}

var maxBandwidth = average + (sd * sigmaThreshold);
return FilterOnBandwidth(events, minBandwidth, maxBandwidth);
var outputEvents = events.Where(ev => ((SpectralEvent)ev).BandWidthHertz > minBandwidth && ((SpectralEvent)ev).BandWidthHertz < maxBandwidth).ToList();
return outputEvents;
}

public static List<EventCommon> FilterOnBandwidth(List<EventCommon> events, double minBandwidth, double maxBandwidth)
Expand Down Expand Up @@ -96,6 +102,35 @@ public static List<EventCommon> FilterOnDuration(List<EventCommon> events, doubl
return outputEvents;
}

/// <summary>
/// Returns the average of the maximum decibel value in each frame of an event.
/// </summary>
/// <param name="ev">The event.</param>
/// <param name="spectrogramData">The spectrogramin decibels.</param>
/// <param name="converter">Converter between real values and spectrogram frames/bins.</param>
/// <returns>The average decibel value.</returns>
public static double GetAverageDecibelsInEvent(SpectralEvent ev, double[,] spectrogramData, UnitConverters converter)
{
// extract the event from the spectrogram
var lowerBin = converter.GetFreqBinFromHertz(ev.LowFrequencyHertz);
var upperBin = converter.GetFreqBinFromHertz(ev.HighFrequencyHertz);
var frameStart = converter.FrameFromStartTime(ev.EventStartSeconds);
var frameEnd = converter.FrameFromStartTime(ev.EventEndSeconds);
var subMatrix = MatrixTools.Submatrix<double>(spectrogramData, frameStart, lowerBin, frameEnd, upperBin);

// extract the decibel array.
int arrayLength = subMatrix.GetLength(0);
var decibelArray = new double[arrayLength];
for (int i = 0; i < arrayLength; i++)
{
var spectralBins = MatrixTools.GetRow(subMatrix, i);
decibelArray[i] = spectralBins.Max();
}

double avDecibels = decibelArray.Average();
return avDecibels;
}

/// <summary>
/// Returns the matrix of neighbourhood values below an event.
/// </summary>
Expand Down Expand Up @@ -274,23 +309,23 @@ public static List<EventCommon> FilterEventsOnNeighbourhoodAverage(
}

/// <summary>
/// Removes events from a list of events that contain excessive noise in the upper neighbourhood.
/// Removes events from a list of events that contain excessive noise in the event side bands; i.e. thhe upper and/or lower neighbouring frequency bands.
/// Excess noise can indicate that this is not a legitimate event.
/// This method counts the bins and frames containing above threshold activity (decibel value) in the buffer zones above and below the events.
/// </summary>
/// <param name="events">A list of spectral events.</param>
/// <param name="spectrogram">The spectrogram in which the event occurs.</param>
/// <param name="spectrogram">The decibel spectrogram in which the events occurs.</param>
/// <param name="lowerHertzBuffer">The band width of the required lower buffer. 100-200Hz is often appropriate.</param>
/// <param name="upperHertzBuffer">The band width of the required upper buffer. 300-500Hz is often appropriate.</param>
/// <param name="decibelThreshold">Threshold noise level - assumed to be in decibels.</param>
/// <param name="decibelBuffer">Minimum required decibel difference between event activity and neighbourhood activity.</param>
/// <returns>A list of filtered events.</returns>
public static List<EventCommon> FilterEventsOnNeighbourhood(
List<SpectralEvent> events,
BaseSonogram spectrogram,
int lowerHertzBuffer,
int upperHertzBuffer,
TimeSpan segmentStartOffset,
double decibelThreshold)
double decibelBuffer)
{
// allow bin gaps above and below the event.
int upperBinGap = 4;
Expand All @@ -305,16 +340,17 @@ public static List<EventCommon> FilterEventsOnNeighbourhood(
var filteredEvents = new List<EventCommon>();
foreach (var ev in events)
{
var matrix = GetNeighbourhoodAsOneMatrix(ev, spectrogram.Data, lowerHertzBuffer, lowerBinGap, upperHertzBuffer, upperBinGap, converter);
var averageRowDecibels = MatrixTools.GetRowAverages(matrix);
var averageColDecibels = MatrixTools.GetColumnAverages(matrix);
int noisyRowCount = averageRowDecibels.Count(x => x > decibelThreshold);
int noisyColCount = averageColDecibels.Count(x => x > decibelThreshold);

// Require that there be at most one buffer bin and one buffer frame containing acoustic activity.
var eventDecibels = GetAverageDecibelsInEvent(ev, spectrogram.Data, converter);
var sidebandMatrix = GetNeighbourhoodAsOneMatrix(ev, spectrogram.Data, lowerHertzBuffer, lowerBinGap, upperHertzBuffer, upperBinGap, converter);
var averageRowDecibels = MatrixTools.GetRowAverages(sidebandMatrix);
var averageColDecibels = MatrixTools.GetColumnAverages(sidebandMatrix);
int noisyRowCount = averageRowDecibels.Count(x => x > (eventDecibels - decibelBuffer));
int noisyColCount = averageColDecibels.Count(x => x > (eventDecibels - decibelBuffer));

// Require that there be at most one buffer bin and one buffer frame containing excessive acoustic activity.
if (noisyRowCount <= 1 && noisyColCount <= 1)
{
// There is little acoustic activity in the upper and lower buffer zones. It is likely to be a discrete event.
// There is reduced acoustic activity in the upper and lower buffer zones. It is likely to be a discrete event.
filteredEvents.Add(ev);
}
}
Expand Down

0 comments on commit 2ee86d8

Please sign in to comment.