Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work on whistle algorithm #477

Merged
merged 5 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ PostProcessing:
# Step 2: Combine possible syllable sequences and filter on excess syllable count.
# Step 3: Remove events whose bandwidth is too small or large.
# Step 4: Remove events that have excessive noise in their side-bands.
PostProcessInDecibelGroups: true
PostProcessInDecibelGroups: false
# 1: Combine overlapping events
CombineOverlappingEvents: true

Expand Down
16 changes: 11 additions & 5 deletions src/AudioAnalysisTools/CommonParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,19 @@ public abstract class CommonParameters : IValidatableObject
/// </summary>
public double? BgNoiseThreshold { get; set; }

/// <summary>snr
/// Gets or sets the bottom bound of the rectangle. Units are Hertz.
/// <summary>
/// Gets or sets the bottom bound of a search band. Units are Hertz.
/// A search band is the frequency band within which an algorithm searches for a particular track or event.
/// This is to be carefully distinguished from the top and bottom bounds of a specific event.
/// A search band consists of two parallel lines/freqeuncy bins.
/// An event is represented by a rectangle.
/// Events will/should always lie within a search band. There may be exception in edge cases, i.e. where an event sits on a search bound.
/// </summary>
public int? MinHertz { get; set; }

/// <summary>
/// Gets or sets the the top bound of the rectangle. Units are Hertz.
/// Gets or sets the the top bound of a search band. Units are Hertz.
/// A search band is the frequency band within which an algorithm searches for a particular track or event.
/// </summary>
public int? MaxHertz { get; set; }

Expand Down Expand Up @@ -95,8 +101,8 @@ public abstract class CommonParameters : IValidatableObject

public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return this.MinHertz.ValidateNotNull(nameof(this.MinHertz));
yield return this.MaxHertz.ValidateNotNull(nameof(this.MaxHertz));
//yield return this.MinHertz.ValidateNotNull(nameof(this.MinHertz));
//yield return this.MaxHertz.ValidateNotNull(nameof(this.MaxHertz));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this validation is disabled?

Revert change or delete validation (and explain).

yield return this.ValidateLessThan(this.MinHertz, nameof(this.MinHertz), this.MaxHertz, nameof(this.MaxHertz));
yield return this.DecibelThresholds.ValidateNotNull(nameof(this.DecibelThresholds));
yield return this.DecibelThresholds.ValidateNotEmpty(nameof(this.DecibelThresholds));
Expand Down
24 changes: 22 additions & 2 deletions src/AudioAnalysisTools/Tracks/MinAndMaxBandwidthParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,38 @@ namespace AnalysisPrograms.Recognizers.Base

public class MinAndMaxBandwidthParameters : CommonParameters
{
/*
/// <summary>
/// Gets or sets the minimum bandwidth, units = Hertz.
/// Gets or sets the bottom bound of a search band. Units are Hertz.
/// A search band is the frequency band within which an algorithm searches for a particular track or event.
/// This is to be carefully distinguished from the top and bottom bounds of a specific event.
/// A search band consists of two parallel lines/freqeuncy bins.
/// An event is represented by a rectangle.
/// Events will/should always lie within a search band. There may be exception in edge cases, i.e. where an event sits on a search bound.
/// </summary>
public int? SearchbandMinHertz { get; set; }

/// <summary>
/// Gets or sets the the top bound of a search band. Units are Hertz.
/// A search band is the frequency band within which an algorithm searches for a particular track or event.
/// </summary>
public int? SearchbandMaxHertz { get; set; }
*/

/// <summary>
/// Gets or sets the minimum allowed bandwidth of a spectrogram track or event, units = Hertz.
/// </summary>
public int? MinBandwidthHertz { get; set; }

/// <summary>
/// Gets or sets maximum bandwidth, units = Hertz.
/// Gets or sets the maximum allowed bandwidth of a spectrogram track or event, units = Hertz.
/// </summary>
public int? MaxBandwidthHertz { get; set; }

public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return this.MinHertz.ValidateNotNull(nameof(this.MinHertz));
yield return this.MaxHertz.ValidateNotNull(nameof(this.MaxHertz));
yield return this.MinBandwidthHertz.ValidateNotNull(nameof(this.MinBandwidthHertz));
yield return this.MaxBandwidthHertz.ValidateNotNull(nameof(this.MaxBandwidthHertz));

Expand Down
34 changes: 28 additions & 6 deletions src/AudioAnalysisTools/Tracks/OnebinTrackAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public static (List<EventCommon> Events, List<Plot> DecibelPlots) GetOnebinTrack
segmentStartOffset,
decibelThreshold.Value);

foreach (var ev in events)
{
ev.Name = profileName;
}

spectralEvents.AddRange(events);

var plot = Plot.PreparePlot(decibelArray, $"{profileName} (Whistles:{decibelThreshold.Value:F0}dB)", decibelThreshold.Value);
Expand All @@ -66,8 +71,22 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)
int binCount = sonogramData.GetLength(1);
int nyquist = sonogram.NyquistFrequency;
double binWidth = nyquist / (double)binCount;
int minBin = (int)Math.Round(parameters.MinHertz.Value / binWidth);
int maxBin = (int)Math.Round(parameters.MaxHertz.Value / binWidth);

// set lower frequency bins of the search band
int minSearchBin = (int)Math.Floor(parameters.SearchbandMinHertz.Value / binWidth);
if (minSearchBin < 1)
{
minSearchBin = 1;
}

// set top search bin allowing for the top sideband.
int maxSearchBin = (int)Math.Floor(parameters.SearchbandMaxHertz.Value / binWidth) - 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The incorrect parameters SearchbandMinHertz and SearchbandMaxHertz are still being used here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the corrections as requested. I have also made an extensive class summary for the OneBinTrackAlgorithm class i.e. the class used to find whistles. I hope this will be helpful in understanding the algorithm and explaining the "magic numbers". In fact all the track algorithms require that a lot of thought be given to the choice of sample rate, frame size and frame step. The typical values are not always appropriate depending on the target call.

if (maxSearchBin > binCount - 6)
{
maxSearchBin = binCount - 6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this magic number 6? Is that the "sideband"? Extract to a constant?

}

// get max and min duration for the whistle event.
double minDuration = parameters.MinDuration.Value;
double maxDuration = parameters.MaxDuration.Value;

Expand All @@ -79,20 +98,23 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)

//Find all bin peaks and place in peaks matrix
var peaks = new double[frameCount, binCount];

// tf = timeframes
for (int tf = 0; tf < frameCount; tf++)
{
for (int bin = minBin + 1; bin < maxBin - 1; bin++)
for (int bin = minSearchBin; bin <= maxSearchBin; bin++)
{
if (sonogramData[tf, bin] < decibelThreshold)
{
continue;
}

// here we define the amplitude profile of a whistle. The buffer zone around whistle is five bins wide.
// here we define the amplitude profile of a whistle.
// The buffer zone around centre of whistle is five bins wide. Ignore bins -2 and +2
var bandIntensity = ((sonogramData[tf, bin - 1] * 0.5) + sonogramData[tf, bin] + (sonogramData[tf, bin + 1] * 0.5)) / 2.0;
var topSidebandIntensity = (sonogramData[tf, bin + 3] + sonogramData[tf, bin + 4] + sonogramData[tf, bin + 5]) / 3.0;
var netAmplitude = 0.0;
if (bin < 4)
if (bin < 5)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More magic numbers

{
netAmplitude = bandIntensity - topSidebandIntensity;
}
Expand Down Expand Up @@ -138,7 +160,7 @@ public static (List<EventCommon> ListOfevents, double[] CombinedIntensityArray)
{
SegmentStartSeconds = segmentStartOffset.TotalSeconds,
SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep,
Name = "Whistle",
Name = "Whistle", // this name can be overridden later.
};

events.Add(ae);
Expand Down
10 changes: 10 additions & 0 deletions src/AudioAnalysisTools/Tracks/OnebinTrackParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ namespace AnalysisPrograms.Recognizers.Base
[YamlTypeTag(typeof(OnebinTrackParameters))]
public class OnebinTrackParameters : CommonParameters
{
/// <summary>
/// Gets or sets a value indicating the minimum Hertz value of the search band.
/// </summary>
public int? SearchbandMinHertz { get; set; }

/// <summary>
/// Gets or sets a value indicating the maximum Hertz value of the search band.
/// </summary>
public int? SearchbandMaxHertz { get; set; }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These do not appear to be used. They're redundant? Remove?

/// <summary>
/// Gets or sets a value indicating whether proximal whistle tracks are to be combined.
/// Proximal means the whistle tracks are in the same frequency band
Expand Down
8 changes: 4 additions & 4 deletions src/AudioAnalysisTools/Tracks/UpwardTrackAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public static (List<EventCommon> Events, double[] CombinedIntensity) GetUpwardTr
var frameStep = sonogram.FrameStep;
int nyquist = sonogram.NyquistFrequency;
double binWidth = nyquist / (double)binCount;
int minBin = (int)Math.Round(parameters.MinHertz.Value / binWidth);
int maxBin = (int)Math.Round(parameters.MaxHertz.Value / binWidth);
int minSearchBin = (int)Math.Round(parameters.MinHertz.Value / binWidth);
int maxSearchBin = (int)Math.Round(parameters.MaxHertz.Value / binWidth);
var minBandwidthHertz = parameters.MinBandwidthHertz ?? throw new ArgumentNullException($"{nameof(UpwardTrackParameters.MinBandwidthHertz)} must be set. Check your config file?");
var maxBandwidthHertz = parameters.MaxBandwidthHertz ?? throw new ArgumentNullException($"{nameof(UpwardTrackParameters.MinBandwidthHertz)} must be set. Check your config file?");

Expand All @@ -90,7 +90,7 @@ public static (List<EventCommon> Events, double[] CombinedIntensity) GetUpwardTr
var peaks = new double[frameCount, binCount];
for (int row = 1; row < frameCount - 1; row++)
{
for (int col = minBin; col < maxBin; col++)
for (int col = minSearchBin; col < maxSearchBin; col++)
{
if (sonogramData[row, col] < decibelThreshold)
{
Expand All @@ -107,7 +107,7 @@ public static (List<EventCommon> Events, double[] CombinedIntensity) GetUpwardTr
}

//NOTE: the Peaks matrix is same size as the sonogram.
var tracks = GetUpwardTracks(peaks, minBin, maxBin, minBandwidthHertz, maxBandwidthHertz, decibelThreshold, converter);
var tracks = GetUpwardTracks(peaks, minSearchBin, maxSearchBin, minBandwidthHertz, maxBandwidthHertz, decibelThreshold, converter);

// initialise tracks as events and get the combined intensity array.
var events = new List<SpectralEvent>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,16 @@ public void TestRecognizer()
// events[2] should be a composite event.
var ev = (CompositeEvent)events[2];
Assert.IsInstanceOfType(events[2], typeof(CompositeEvent));
Assert.AreEqual(22, ev.EventStartSeconds, TestHelper.AllowedDelta);
Assert.AreEqual(22, ev.EventEndSeconds, TestHelper.AllowedDelta);
Assert.AreEqual(22.000, ev.EventStartSeconds, TestHelper.AllowedDelta);
Assert.AreEqual(22.368, ev.EventEndSeconds, TestHelper.AllowedDelta);
Assert.AreEqual(4743, ev.BandWidthHertz);

var componentEvents = ev.ComponentEvents;
Assert.AreEqual(3, componentEvents.Count);
//Assert.AreEqual(3, componentEvents.Count);
Assert.AreEqual(13, componentEvents.Count);

// This tests that the component tracks are correctly combined.
//This can also be tested somewhere else, starting with just the comosite event in json file.
//This can also be tested somewhere else, starting with just the composite event in json file.
var points = EventExtentions.GetCompositeTrack(componentEvents.Cast<WhipEvent>()).ToArray();
Assert.AreEqual(22.016, points[1].Seconds.Minimum, TestHelper.AllowedDelta);
Assert.AreEqual(5456, points[1].Hertz.Minimum);
Expand Down
Loading