Skip to content

Commit

Permalink
Set up post-processing options for Boobook Owl
Browse files Browse the repository at this point in the history
Issue #319 Rework algorithm files so there is consistency re post-processing.
Have only changed the Boobook owl recognizer so far.
  • Loading branch information
towsey authored and atruskie committed May 29, 2020
1 parent c680408 commit 8c94143
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

# Resample rate must be 2 X the desired Nyquist
#ResampleRate: 22050
#ResampleRate: 16000
# SegmentDuration: units=seconds;
SegmentDuration: 60
# SegmentOverlap: units=seconds;
SegmentOverlap: 0

# Preprocessing of the recording segment
IgnoreHighAmplitudeClippedRecordingSegments: true

# Each of these profiles will be analyzed
Profiles:
BoobookSyllable: !ForwardTrackParameters
Expand All @@ -18,34 +20,41 @@ Profiles:
WindowFunction: HANNING
BgNoiseThreshold: 0.0
# min and max of the freq band to search
MinHertz: 300
MinHertz: 400
MaxHertz: 1000
MinDuration: 0.17
MaxDuration: 1.2
DecibelThreshold: 9.0

#Combine each pair of Boobook syllables as one event
#CombineProximalSimilarEvents: false

#################### POST-PROCESSING of EVENTS ###################

# A: First post-processing steps are to combine overlapping/proximal/sequential events
# 1: Combine overlapping events
#CombineOverlappingEvents: false

# 2: Combine each pair of Boobook syllables as one event
CombinePossibleSyllableSequence: true
SyllableStartDifference: 0.6
SyllableHertzGap: 350

#CombineOverlappedEvents: false
# Common settings
#Standard: &STANDARD
#EventThreshold: 0.2
#BgNoiseThreshold: 3.0
# 3: Combine events that are likely to be repetitions of the same syllable.
CombineProximalSimilarEvents: false

# This notation means the a profile has all of the settings that the Standard profile has,
# however, the DctDuration parameter has been overridden.
# <<: *STANDARD
# DctDuration: 0.3
# B: Remaining post-processing steps are to filter out over/undersize events
# 4: Filter the events for duration in seconds
RemoveEventsHavingWrongDuration: true

# Available options (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
# 5: Filter the events for bandwidth in Hertz
RemoveEventsHavingWrongBandwidth: true

# C: Options to save results files
# 6: Available options for saving data files (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
SaveIntermediateWavFiles: Never
SaveIntermediateCsvFiles: false
# Available options (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
# "True" is useful when debugging but "WhenEventsDetected" is required for operational use.

# 7: Available options for saving
#SaveSonogramImages: True
SaveSonogramImages: WhenEventsDetected
# DisplayCsvImage is obsolete - ensure it remains set to: false
Expand Down
29 changes: 11 additions & 18 deletions src/AnalysisPrograms/Recognizers/Birds/NinoxBoobook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,18 @@ public override RecognizerResults Recognize(
outputDirectory,
imageWidth);

// DO POST-PROCESSING of EVENTS
// ################### DO POST-PROCESSING of EVENTS ###################

var configuration = (GenericRecognizerConfig)genericConfig;
var chirpConfig = (ForwardTrackParameters)configuration.Profiles["BoobookSyllable"];

// Filter out the chirp events for possible combining.
// 1: Pull out the chirp events for possible combining.
var (chirpEvents, others) = combinedResults.NewEvents.FilterForEventType<ChirpEvent, EventCommon>();

// Uncomment the next line when want to obtain the event frequency profiles.
// WriteFrequencyProfiles(chirpEvents);

// Calculate frequency profile score for each event
// 2: Calculate frequency profile score for each event
foreach (var ev in chirpEvents)
{
SetFrequencyProfileScore((ChirpEvent)ev);
Expand All @@ -115,28 +115,20 @@ public override RecognizerResults Recognize(
//var newEvents = spectralEvents.Cast<EventCommon>().ToList();
//var spectralEvents = events.Select(x => (SpectralEvent)x).ToList();

// Combine overlapping events. If the dB threshold is set low, may get lots of little events.
// 3: Combine overlapping events. If the dB threshold is set low, may get lots of little events.
var combinedEvents = CompositeEvent.CombineOverlappingEvents(chirpEvents.Cast<EventCommon>().ToList());

// DO POST-PROCESSING of EVENTS
//var events = combinedResults.NewEvents;
List<EventCommon> newEvents;

// NOTE: If the dB threshold is set low, may get lots of little events.
// 4: Combine proximal events. If the dB threshold is set low, may get lots of little events.
if (genericConfig.CombinePossibleSyllableSequence)
{
// Convert events to spectral events for combining of possible sequences.
var spectralEvents = combinedEvents.Cast<SpectralEvent>().ToList();
var startDiff = genericConfig.SyllableStartDifference;
var hertzDiff = genericConfig.SyllableHertzGap;
newEvents = CompositeEvent.CombineSimilarProximalEvents(spectralEvents, TimeSpan.FromSeconds(startDiff), (int)hertzDiff);
}
else
{
newEvents = combinedEvents;
combinedEvents = CompositeEvent.CombineSimilarProximalEvents(spectralEvents, TimeSpan.FromSeconds(startDiff), (int)hertzDiff);
}

//filter the events for duration in seconds
// 5: Filter the events for duration in seconds
var minimumEventDuration = chirpConfig.MinDuration;
var maximumEventDuration = chirpConfig.MaxDuration;
if (genericConfig.CombinePossibleSyllableSequence)
Expand All @@ -145,10 +137,11 @@ public override RecognizerResults Recognize(
maximumEventDuration *= 1.5;
}

combinedResults.NewEvents = SpectralEvent.FilterOnDuration(newEvents, minimumEventDuration.Value, maximumEventDuration.Value);
combinedResults.NewEvents = SpectralEvent.FilterOnDuration(combinedEvents, minimumEventDuration.Value, maximumEventDuration.Value);

double average = 245;
double sd = 15;
// 6: Filter the events for bandwidth in Hertz
double average = 270;
double sd = 20;
double sigmaThreshold = 3.0;
combinedResults.NewEvents = SpectralEvent.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold);

Expand Down
76 changes: 76 additions & 0 deletions src/AudioAnalysisTools/Events/SpectralEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ namespace AudioAnalysisTools.Events
{
using System;
using System.Collections.Generic;
using System.Linq;
using AnalysisBase.ResultBases;
using AudioAnalysisTools.Events.Drawing;
using AudioAnalysisTools.Events.Interfaces;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using TowseyLibrary;

public class SpectralEvent : EventCommon, ISpectralEvent, ITemporalEvent
{
Expand Down Expand Up @@ -133,5 +135,79 @@ public static List<EventCommon> FilterOnDuration(List<EventCommon> events, doubl

return outputEvents;
}

/// <summary>
/// Calculates the average amplitude in the frequency bins just above the event.
/// If it contains above threshold acoustic content, this is unlikely to be a discrete event.
/// </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 the event.</returns>
public static double GetAverageAmplitudeInUpperNeighbourhood(SpectralEvent ev, double[,] sonogramData, int bufferBins, UnitConverters converter)
{
// allow a gap of three bins above the event.
int gap = 3;
var bottomBufferBin = converter.GetFreqBinFromHertz(ev.HighFrequencyHertz) + gap;
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;
}

/// <summary>
/// Calculates the average amplitude in the frequency bins just below the event.
/// If it contains above threshold acoustic content, this is unlikely to be a discrete event.
/// </summary>
/// <param name="ev">The event.</param>
/// <param name="sonogramData">The spectrogram data as matrix with origin top/left.</param>
/// <param name="bufferBins">The bandwidth 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 below the event.</returns>
public static double GetAverageAmplitudeInLowerNeighbourhood(SpectralEvent ev, double[,] sonogramData, int bufferBins, UnitConverters converter)
{
int gap = 1;
var topBufferBin = converter.GetFreqBinFromHertz(ev.LowFrequencyHertz) - gap;
var bottomBufferBin = topBufferBin - bufferBins;
bottomBufferBin = Math.Max(0, bottomBufferBin);
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;
}

/// <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.
/// </summary>
/// <param name="events">A list of spectral events.</param>
/// <param name="sonogramData">A matrix of the spectrogram in which event occurs.</param>
/// <param name="bufferHertz">The band width of the required buffer. 300-500Hz is often appropriate.</param>
/// <param name="converter">Converts sec/Hz to frame/bin.</param>
/// <returns>A list of filtered events.</returns>
public static List<EventCommon> FilterEventsOnUpperNeighbourhood(List<SpectralEvent> events, double[,] sonogramData, int bufferHertz, UnitConverters converter, double decibelThreshold)
{
var bufferBins = (int)Math.Round(bufferHertz / converter.HertzPerFreqBin);
var filteredEvents = new List<EventCommon>();
foreach (var ev in events)
{
var avUpperNhAmplitude = SpectralEvent.GetAverageAmplitudeInUpperNeighbourhood((SpectralEvent)ev, sonogramData, bufferBins, converter);
Console.WriteLine($"################################### Buffer Average decibels = {avUpperNhAmplitude}");

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

return filteredEvents;
}
}
}
6 changes: 5 additions & 1 deletion src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public static (List<EventCommon> Events, double[] CombinedIntensity) GetForwardT
double maxDuration = parameters.MaxDuration.Value;
double decibelThreshold = parameters.DecibelThreshold.Value;

// ################# the following should be a user parameter.
var bufferHertz = 300;

var converter = new UnitConverters(
segmentStartOffset: segmentStartOffset.TotalSeconds,
sampleRate: sonogram.SampleRate,
Expand Down Expand Up @@ -118,8 +121,9 @@ public static (List<EventCommon> Events, double[] CombinedIntensity) GetForwardT
// This will help in some cases to combine related events.
if (parameters.CombinePossibleSyllableSequence)
{
List<SpectralEvent> se = events.Cast<SpectralEvent>().ToList();
var timeDiff = TimeSpan.FromSeconds(parameters.SyllableStartDifference);
returnEvents = CompositeEvent.CombineSimilarProximalEvents(events, timeDiff, parameters.SyllableHertzGap);
returnEvents = CompositeEvent.CombineSimilarProximalEvents(se, timeDiff, parameters.SyllableHertzGap);
}

return (returnEvents, combinedIntensityArray);
Expand Down
42 changes: 1 addition & 41 deletions src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ namespace AudioAnalysisTools.Tracks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Acoustics.Shared;
using AnalysisPrograms.Recognizers.Base;
using AudioAnalysisTools.Events;
using AudioAnalysisTools.Events.Tracks;
using AudioAnalysisTools.StandardSpectrograms;
using TowseyLibrary;
using TrackType = AudioAnalysisTools.Events.Tracks.TrackType;

public static class OnebinTrackAlgorithm
Expand Down Expand Up @@ -108,24 +106,7 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)
var hertzDifference = 4 * binWidth;
var whistleEvents = WhistleEvent.CombineAdjacentWhistleEvents(events, hertzDifference);

// 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);
return (whistleEvents, combinedIntensityArray);
}

/// <summary>
Expand Down Expand Up @@ -209,26 +190,5 @@ 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class BoobookOwlTests : OutputDirectoryTest
/// <summary>
/// The canonical recording used for this recognizer is a 31 second recording made by Yvonne Phillips at Gympie National Park, 2015-08-18.
/// </summary>
private static readonly FileInfo TestAsset = PathHelper.ResolveAsset("Recordings", "gympie_np_1192_331618_20150818_054959_31_0.wav");
//private static readonly FileInfo TestAsset = new FileInfo(@"C:\Ecoacoustics\WavFiles\CottonProject\Boobook\BAC2_20071018-145040_Speech.wav");
//private static readonly FileInfo TestAsset = PathHelper.ResolveAsset("Recordings", "gympie_np_1192_331618_20150818_054959_31_0.wav");
private static readonly FileInfo TestAsset = new FileInfo(@"C:\Ecoacoustics\WavFiles\CottonProject\Boobook\NZ_Parsons_201211_211502_3-6minutes.wav");
private static readonly FileInfo ConfigFile = PathHelper.ResolveConfigFile("RecognizerConfigFiles", "Towsey.NinoxBoobook.yml");
private static readonly AudioRecording Recording = new AudioRecording(TestAsset);
private static readonly NinoxBoobook Recognizer = new NinoxBoobook();
Expand Down

0 comments on commit 8c94143

Please sign in to comment.