Skip to content

Commit

Permalink
Change the filter for syllable sequences
Browse files Browse the repository at this point in the history
Issue #370 Previously the filter was based on component events but now that we have multiple decibel thresholds, composite events are accumulating more component events, so now need to calculate the number of probable syllables within a composite event.
  • Loading branch information
towsey authored and atruskie committed Oct 14, 2020
1 parent 55ddfad commit 773181d
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 22 deletions.
8 changes: 2 additions & 6 deletions src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,11 @@ public override RecognizerResults Recognize(
// Now filter on properties of the sequences which are treated as Composite events.
if (sequenceConfig.FilterSyllableSequence)
{
// filter on number of components
// filter on number of syllables and their periodicity.
var maxComponentCount = sequenceConfig.SyllableMaxCount;
allResults.NewEvents = EventFilters.FilterEventsOnComponentCount(allResults.NewEvents, maxComponentCount);
Log.Debug($"Event count after filtering on component count = {allResults.NewEvents.Count}");

// filter on syllable periodicity
var period = sequenceConfig.ExpectedPeriod;
var periodSd = sequenceConfig.PeriodStandardDeviation;
allResults.NewEvents = EventFilters.FilterEventsOnSyllablePeriodicity(allResults.NewEvents, period, periodSd);
allResults.NewEvents = EventFilters.FilterEventsOnSyllableCountAndPeriodicity(allResults.NewEvents, maxComponentCount, period, periodSd);
Log.Debug($"Event count after filtering on periodicity = {allResults.NewEvents.Count}");
}
}
Expand Down
140 changes: 127 additions & 13 deletions src/AudioAnalysisTools/Events/EventFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace AudioAnalysisTools.Events
using System.Linq;
using AudioAnalysisTools.Events.Types;
using AudioAnalysisTools.StandardSpectrograms;
using MoreLinq;
using TowseyLibrary;

public static class EventFilters
Expand Down Expand Up @@ -102,7 +103,7 @@ public static List<EventCommon> FilterOnDuration(List<EventCommon> events, doubl
/// <summary>
/// Removes composite events from a list of EventCommon that contain more than the specfied number of SpectralEvent components.
/// </summary>
public static List<EventCommon> FilterEventsOnCompositeContent(
public static List<EventCommon> FilterEventsOnComponentCount(
List<EventCommon> events,
int maxComponentCount)
{
Expand All @@ -125,33 +126,146 @@ public static List<EventCommon> FilterEventsOnCompositeContent(
/// <summary>
/// Removes composite events from a list of EventCommon where the component syllables do not have the correct periodicity.
/// </summary>
public static List<EventCommon> FilterEventsOnSyllablePeriodicity(
List<EventCommon> events,
double expectedPeriod,
double periodSd)
public static List<EventCommon> FilterEventsOnSyllableCountAndPeriodicity(List<EventCommon> events, int maxSyllableCount, double expectedPeriod, double expectedSd)
{
var minExpectedPeriod = expectedPeriod - (3 * expectedSd);
var maxExpectedPeriod = expectedPeriod + (3 * expectedSd);

var filteredEvents = new List<EventCommon>();

foreach (var ev in events)
{
if (ev is CompositeEvent)
// ignore non-composite events
if (ev is CompositeEvent == false)
{
var actualPeriodicity = ((CompositeEvent)ev).CalculatePeriodicity();
var minAllowedPeriodicity = expectedPeriod - (3 * periodSd);
var maxAllowedPeriodicity = expectedPeriod + (3 * periodSd);
if (actualPeriodicity < minAllowedPeriodicity || actualPeriodicity > maxAllowedPeriodicity)
filteredEvents.Add(ev);
continue;
}

// Get the temporal footprint of the component events.
(bool[] temporalFootprint, double timeScale) = GetTemporalFootprint(ev);

// calculate the periodicity in seconds
int syllableCount = 1;
var periodSeconds = new List<double>();
int previousEventStart = 0;
for (int f = 1; f < temporalFootprint.Length; f++)
{
if (temporalFootprint[f] && !temporalFootprint[f - 1])
{
// ignore composite events which do not have the correct periodicity
continue;
// calculate the event interval in seconds.
syllableCount++;
periodSeconds.Add((f - previousEventStart + 1) * timeScale);
previousEventStart = f;
}
}

filteredEvents.Add(ev);
// reject composite events whose total syllable count exceeds the user defined max.
if (syllableCount > maxSyllableCount)
{
continue;
}

// now filter on syllable periodicity.
if (syllableCount == 1)
{
// there was only one event - the multiple events all overlapped as one event
// accept this as valid outcome. There is no interval on which to filter.
filteredEvents.Add(ev);
}
else
{
if (syllableCount == 2)
{
// there were only two events, with one interval
// accept this as valid outcome, iff the interval falls within the expected interval.
var actualInterval = periodSeconds[0];

if (actualInterval >= minExpectedPeriod && actualInterval <= maxExpectedPeriod)
{
filteredEvents.Add(ev);
}
}
else
{
// there were more than two events. Require overlap between actual and expected ranges.
NormalDist.AverageAndSD(periodSeconds.ToArray(), out double averagePeriod, out double sdPeriod);

// get the difference between the expected and absolute periods.
var periodDifference = Math.Abs(averagePeriod - expectedPeriod);

//This difference should be less than the combined SDs.
var combinedSds = (sdPeriod + expectedSd) * 2;
if (periodDifference <= combinedSds)
{
filteredEvents.Add(ev);
}
}
}
}

return filteredEvents;
}

public static (bool[] TemporalFootprint, double TimeScale) GetTemporalFootprint(EventCommon compositeEvent)
{
if (compositeEvent is CompositeEvent == false)
{
throw new Exception("Invalid event type. Event passed to GetTemporalFotprint() must be of type CompositeEvent.");
}

// get the composite events.
var events = ((CompositeEvent)compositeEvent).ComponentEvents;

var startEnds = new List<double[]>();
double firstStart = double.MaxValue;
double lastEnd = 0.0;

foreach (var ev in events)
{
var startAndDuration = new double[2] { ev.EventStartSeconds, ((SpectralEvent)ev).EventDurationSeconds };
startEnds.Add(startAndDuration);

if (firstStart > ev.EventStartSeconds)
{
firstStart = ev.EventStartSeconds;
}

if (lastEnd < ((SpectralEvent)ev).EventEndSeconds)
{
lastEnd = ((SpectralEvent)ev).EventEndSeconds;
}
}

// set up a temporal array to contain event footprint info.
int arrayLength = 100;
bool[] temporalFootprint = new bool[arrayLength];
var compositeTimeDuration = lastEnd - firstStart;
var timeScale = compositeTimeDuration / (double)arrayLength;

foreach (var pair in startEnds)
{

int startFrame = (int)Math.Floor((pair[0] - firstStart) / timeScale);
int endFrame = startFrame - 1 + (int)Math.Floor(pair[1] / timeScale);

for (int f = startFrame; f <= endFrame; f++)
{
temporalFootprint[f] = true;
}
}

return (temporalFootprint, timeScale);
}

public static (int Count, double AveragePeriod, double SdPeriod) GetPeriodicity(bool[] temporalFootprint, double timeScale)
{
int count = 0;
double averagePeriod = 0.0;
double sdPeriod = 0.0;
return (count, averagePeriod, sdPeriod);
}

/// <summary>
/// Removes events from a list of events that contain excessive noise in the upper neighbourhood.
/// Excess noise can indicate that this is not a legitimate event.
Expand Down
7 changes: 4 additions & 3 deletions src/AudioAnalysisTools/Events/Types/CompositeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ public static List<EventCommon> CombineOverlappingEvents(List<EventCommon> event
/// NOTE: Proximal means (1) that the event starts are close to one another and (2) the events occupy a SIMILAR frequency band.
/// NOTE: SIMILAR frequency band means the difference between two top Hertz values and the two low Hertz values are less than hertzDifference.
/// NOTE: This method is used to combine events that are likely to be a syllable sequence within the same call.
/// NOTE: Allow twice the tolerance for the upper Hertz difference because upper bounds tend to be more flexible. This is may need to be reversed if it proves to be unhelpful.
/// </summary>
public static List<EventCommon> CombineProximalEvents(List<SpectralEvent> events, TimeSpan startDifference, int hertzDifference)
{
Expand All @@ -228,9 +229,9 @@ public static List<EventCommon> CombineProximalEvents(List<SpectralEvent> events
{
for (int j = i - 1; j >= 0; j--)
{
bool eventStartsAreProximal = Math.Abs(events[i].EventStartSeconds - events[j].EventStartSeconds) < startDifference.TotalSeconds;
bool eventMinimaAreSimilar = Math.Abs(events[i].LowFrequencyHertz - events[j].LowFrequencyHertz) < hertzDifference;
bool eventMaximaAreSimilar = Math.Abs(events[i].HighFrequencyHertz - events[j].HighFrequencyHertz) < hertzDifference;
bool eventStartsAreProximal = Math.Abs(events[i].EventStartSeconds - events[j].EventStartSeconds) <= startDifference.TotalSeconds;
bool eventMinimaAreSimilar = Math.Abs(events[i].LowFrequencyHertz - events[j].LowFrequencyHertz) <= hertzDifference;
bool eventMaximaAreSimilar = Math.Abs(events[i].HighFrequencyHertz - events[j].HighFrequencyHertz) <= (hertzDifference * 2);
if (eventStartsAreProximal && eventMinimaAreSimilar && eventMaximaAreSimilar)
{
var compositeEvent = CombineTwoEvents(events[i], events[j]);
Expand Down

0 comments on commit 773181d

Please sign in to comment.