Skip to content

Commit

Permalink
Work on Whistle recognizer
Browse files Browse the repository at this point in the history
Issue #281 Work on whistle recognizer and give notice of harmonic recognizer
  • Loading branch information
towsey committed Feb 4, 2020
1 parent feb72a0 commit 9511e91
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 42 deletions.
6 changes: 3 additions & 3 deletions src/AnalysisBase/AnalysisCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ private static void ValidateResult<T>(
{
Contract.Ensures(
eventBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds,
"Every event detected by this analysis should of been found within the bounds of the segment analyzed");
"Every event detected by this analysis should be found within the bounds of the segment analyzed");

// ReSharper disable CompareOfFloatsByEqualityOperator
Contract.Ensures(
Expand All @@ -467,14 +467,14 @@ private static void ValidateResult<T>(
{
Contract.Ensures(
summaryIndexBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds,
"Every summary index generated by this analysis should have been found within the bounds of the segment analyzed");
"Every summary index generated by this analysis should be found within the bounds of the segment analyzed");
}

foreach (var spectralIndexBase in result.SpectralIndices)
{
Contract.Ensures(
spectralIndexBase.ResultStartSeconds >= result.SegmentStartOffset.TotalSeconds,
"Every spectral index generated by this analysis should have been found within the bounds of the segment analyzed");
"Every spectral index generated by this analysis should be found within the bounds of the segment analyzed");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Profiles:
# min and max of the freq band to search
MinHertz: 800
MaxHertz: 8000
BottomHertzBuffer: 400
BottomHertzBuffer: 0
TopHertzBuffer: 0
MinDuration: 0.15
MaxDuration: 0.8
Expand Down Expand Up @@ -63,8 +63,8 @@ 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.
# SaveSonogramImages: True
SaveSonogramImages: WhenEventsDetected
SaveSonogramImages: True
#SaveSonogramImages: WhenEventsDetected
# DisplayCsvImage is obsolete - ensure it remains set to: false
DisplayCsvImage: false
## End section for AnalyzeLongRecording
Expand Down
1 change: 1 addition & 0 deletions src/AnalysisPrograms/AnalysisPrograms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@
<Compile Include="Recognizers\Base\RecognizerBase.cs" />
<Compile Include="Recognizers\Base\RecognizerEntry.cs" />
<Compile Include="Recognizers\Base\RecognizerResults.cs" />
<Compile Include="Recognizers\Base\HarmonicParameters.cs" />
<Compile Include="Recognizers\CriniaTinnula_OBSOLETE.cs" />
<Compile Include="Recognizers\GenericRecognizer.cs" />
<Compile Include="Recognizers\IctalurusFurcatus.cs" />
Expand Down
19 changes: 19 additions & 0 deletions src/AnalysisPrograms/Recognizers/Base/HarmonicParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HarmonicParameters.cs" company="QutEcoacoustics">
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
// </copyright>
// <summary>

namespace AnalysisPrograms.Recognizers.Base
{
using Acoustics.Shared;

/// <summary>
/// Parameters needed from a config file to detect the stacked harmonic components of a soundscape.
/// This can also be used for recognizing the harmonics of non-biological sounds such as from turbines, motor-bikes, compressors and other hi-revving motors.
/// </summary>
[YamlTypeTag(typeof(HarmonicParameters))]
public class HarmonicParameters : CommonParameters
{
}
}
116 changes: 114 additions & 2 deletions src/AnalysisPrograms/Recognizers/Base/WhistleParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,127 @@
// </copyright>
// <summary>

using Acoustics.Shared;
using AudioAnalysisTools;
using AudioAnalysisTools.StandardSpectrograms;
using System;
using System.Collections.Generic;
using TowseyLibrary;

namespace AnalysisPrograms.Recognizers.Base
{
using Acoustics.Shared;

/// <summary>
/// Parameters needed from a config file to detect whistle components.
/// </summary>
[YamlTypeTag(typeof(WhistleParameters))]
public class WhistleParameters : CommonParameters
{

/// <summary>
/// Calculates the mean intensity in a freq band defined by its min and max freq.
/// THis method averages dB log values incorrectly but it is faster than doing many log conversions.
/// This method is used to find acoustic events and is accurate enough for the purpose.
/// </summary>
public static (List<AcousticEvent>, double[]) GetWhistles(SpectrogramStandard sonogram, int minHz, int maxHz, int nyquist, double decibelThreshold, double minDuration, double maxDuration)
{
var sonogramData = sonogram.Data;
int frameCount = sonogramData.GetLength(0);
int binCount = sonogramData.GetLength(1);

double binWidth = nyquist / (double)binCount;
int minBin = (int)Math.Round(minHz / binWidth);
int maxBin = (int)Math.Round(maxHz / binWidth);
//int binCountInBand = maxBin - minBin + 1;

// buffer zone around whistle is three bins wide.
int N = 3;

// list of accumulated acoustic events
var events = new List<AcousticEvent>();
var combinedIntensityArray = new double[frameCount];

// for all frequency bins
for (int bin = minBin; bin < maxBin; bin++)
{
// set up an intensity array for the frequency bin.
double[] intensity = new double[frameCount];

if (minBin < N)
{
// for all time frames in this frequency bin
for (int t = 0; t < frameCount; t++)
{
var sideBandIntensity = ((0.5 * sonogramData[t, bin + 2]) + sonogramData[t, bin + 3]) / (double)2.0;
intensity[t] = sonogramData[t, bin] - sideBandIntensity;
//LoggedConsole.WriteLine($"t{t} bin{bin} intensity = {sonogramData[t, bin]} minus {sideBandIntensity}");
intensity[t] = Math.Max(0.0, intensity[t]);
}
}
else
{
// for all time frames in this frequency bin
for (int t = 0; t < frameCount; t++)
{
var sideBandIntensity = ((0.5 * sonogramData[t, bin + 2]) + sonogramData[t, bin + 3] + (0.5 * sonogramData[t, bin - 2]) + sonogramData[t, bin - 3]) / (double)4.0;
intensity[t] = sonogramData[t, bin] - sideBandIntensity;
intensity[t] = Math.Max(0.0, intensity[t]);
}
}

// smooth the decibel array to allow for brief gaps.
intensity = DataTools.filterMovingAverageOdd(intensity, 5);

//extract the events based on length and threshhold.
// Note: This method does NOT do prior smoothing of the dB array.
// TODO check next line
var segmentStartOffset = TimeSpan.Zero;
var acousticEvents = AcousticEvent.ConvertScoreArray2Events(
intensity,
minHz,
maxHz,
sonogram.FramesPerSecond,
sonogram.FBinWidth,
decibelThreshold,
minDuration,
maxDuration,
segmentStartOffset);

// add to conbined intensity array
for (int t = 0; t < frameCount; t++)
{
//combinedIntensityArray[t] += intensity[t];
combinedIntensityArray[t] = Math.Max(intensity[t], combinedIntensityArray[t]);
}

// combine events
events.AddRange(acousticEvents);
} //end for all freq bins

return (events, combinedIntensityArray);
}

/*
/// <summary>
/// Calculates the average intensity in a freq band having min and max freq,
/// AND then subtracts average intensity in the side/buffer bands, below and above.
/// THis method adds dB log values incorrectly but it is faster than doing many log conversions.
/// This method is used to find acoustic events and is accurate enough for the purpose.
/// </summary>
public static double[] CalculateFreqBandAvIntensityMinusBufferIntensity(double[,] sonogramData, int minHz, int maxHz, int nyquist)
{
var bandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, minHz, maxHz, nyquist);
var bottomSideBandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, minHz - bottomHzBuffer, minHz, nyquist);
var topSideBandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, maxHz, maxHz + topHzBuffer, nyquist);
int frameCount = sonogramData.GetLength(0);
double[] netIntensity = new double[frameCount];
for (int i = 0; i < frameCount; i++)
{
netIntensity[i] = bandIntensity[i] - bottomSideBandIntensity[i] - topSideBandIntensity[i];
}
return netIntensity;
}
*/
}
}
90 changes: 60 additions & 30 deletions src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,52 +94,33 @@ public override RecognizerResults Recognize(
algorithmName = "Oscillation";
break;
case WhistleParameters _:
throw new NotImplementedException("The whistle algorithm has not been implemented yet");
algorithmName = "Whistle";
break;
case HarmonicParameters _:
throw new NotImplementedException("The harmonic algorithm has not been implemented yet");
break;
case Aed.AedConfiguration _:
algorithmName = "AED";
break;
default:
var allowedAlgorithms =
$"{nameof(BlobParameters)}, {nameof(OscillationParameters)}, {nameof(WhistleParameters)}, {nameof(Aed.AedConfiguration)}";
$"{nameof(BlobParameters)}, {nameof(OscillationParameters)}, {nameof(WhistleParameters)}, {nameof(HarmonicParameters)}, {nameof(Aed.AedConfiguration)}";
throw new ConfigFileException($"The algorithm type in profile {profileName} is not recognized. It must be one of {allowedAlgorithms}");
}

Log.Debug($"Using the {algorithmName} algorithm... ");
if (profileConfig is CommonParameters parameters)
{
if (profileConfig is BlobParameters || profileConfig is OscillationParameters)
if (profileConfig is BlobParameters || profileConfig is OscillationParameters || profileConfig is WhistleParameters || profileConfig is HarmonicParameters)
{
sonogram = new SpectrogramStandard(
ParametersToSonogramConfig(parameters),
audioRecording.WavReader);

//get the array of intensity values minus intensity in side/buffer bands.
//i.e. require silence in side-bands. Otherwise might simply be getting part of a broader band acoustic event.
var decibelArray = SNR.CalculateFreqBandAvIntensityMinusBufferIntensity(
sonogram.Data,
parameters.MinHertz,
parameters.MaxHertz,
parameters.BottomHertzBuffer,
parameters.TopHertzBuffer,
sonogram.NyquistFrequency);

// prepare plots
// AT: magic number `3`?
double intensityNormalizationMax = 3 * parameters.DecibelThreshold;
var eventThreshold = parameters.DecibelThreshold / intensityNormalizationMax;
var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, 0, intensityNormalizationMax);
var plot = new Plot($"{profileName} ({algorithmName}:Intensity)", normalisedIntensityArray, eventThreshold);
plots.Add(plot);
sonogram = new SpectrogramStandard(ParametersToSonogramConfig(parameters), audioRecording.WavReader);

if (profileConfig is OscillationParameters op)
{
Oscillations2012.Execute(
sonogram,
op.MinHertz,
op.MaxHertz,

//op.DecibelThreshold,
op.DctDuration,
op.MinOscillationFrequency,
op.MaxOscillationFrequency,
Expand All @@ -159,7 +140,26 @@ public override RecognizerResults Recognize(
}
else if (profileConfig is BlobParameters bp)
{
// iii: CONVERT decibel SCORES TO ACOUSTIC EVENTS
//get the array of intensity values minus intensity in side/buffer bands.
//i.e. require silence in side-bands. Otherwise might simply be getting part of a broader band acoustic event.
var decibelArray = SNR.CalculateFreqBandAvIntensityMinusBufferIntensity(
sonogram.Data,
parameters.MinHertz,
parameters.MaxHertz,
parameters.BottomHertzBuffer,
parameters.TopHertzBuffer,
sonogram.NyquistFrequency);

// prepare plot of resultant blob decibel array.
// to obtain more useful display, set the maximum display value to be 3x threshold value.
double intensityNormalizationMax = 3 * parameters.DecibelThreshold;
var eventThreshold = parameters.DecibelThreshold / intensityNormalizationMax;
var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, 0, intensityNormalizationMax);
var plot = new Plot($"{profileName} ({algorithmName}:db Intensity)", normalisedIntensityArray, eventThreshold);
plots.Add(plot);

// iii: CONVERT blob decibel SCORES TO ACOUSTIC EVENTS.
// Note: This method does NOT do prior smoothing of the dB array.
acousticEvents = AcousticEvent.GetEventsAroundMaxima(
decibelArray,
segmentStartOffset,
Expand All @@ -171,6 +171,39 @@ public override RecognizerResults Recognize(
sonogram.FramesPerSecond,
sonogram.FBinWidth);
}
else if (profileConfig is WhistleParameters wp)
{
double[] decibelArray;
//get the array of intensity values minus intensity in side/buffer bands.
(acousticEvents, decibelArray) = WhistleParameters.GetWhistles(
sonogram,
parameters.MinHertz,
parameters.MaxHertz,
sonogram.NyquistFrequency,
wp.DecibelThreshold,
wp.MinDuration,
wp.MaxDuration);

// prepare plot of resultant whistle decibel array.
// to obtain more useful display, set the maximum display value to be 3x threshold value.
double intensityNormalizationMax = 3 * parameters.DecibelThreshold;
var eventThreshold = parameters.DecibelThreshold / intensityNormalizationMax;
var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, 0, intensityNormalizationMax);
var plot = new Plot($"{profileName} ({algorithmName}:dB Intensity)", normalisedIntensityArray, eventThreshold);
plots.Add(plot);

//// iii: CONVERT whistle decibel scores TO ACOUSTIC EVENTS
//acousticEvents = AcousticEvent.GetEventsAroundMaxima(
// decibelArray,
// segmentStartOffset,
// wp.MinHertz,
// wp.MaxHertz,
// wp.DecibelThreshold,
// wp.MinDuration.Seconds(),
// wp.MaxDuration.Seconds(),
// sonogram.FramesPerSecond,
// sonogram.FBinWidth);
}
else
{
throw new InvalidOperationException();
Expand All @@ -181,9 +214,6 @@ public override RecognizerResults Recognize(
throw new InvalidOperationException();
}

// AT: disabled - the current method definition does not make sense
//acousticEvents = RecognizerTools.FilterEventsForSpectralProfile(acousticEvents, sonogram);

//iV add additional info to the acoustic events
acousticEvents.ForEach(ae =>
{
Expand Down
11 changes: 7 additions & 4 deletions src/AudioAnalysisTools/AcousticEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,10 +1283,13 @@ public static List<AcousticEvent> ConvertScoreArray2Events(
}

av /= i - startFrame + 1;
if (av < scoreThreshold)
{
continue; //skip events whose score is < the threshold
}

//NOTE av cannot be < threhsold because event started and ended based on threhsold.
// Therefore remove the following condition on 04/02/2020
//if (av < scoreThreshold)
//{
// continue; //skip events whose score is < the threshold
//}

AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz);

Expand Down
Loading

0 comments on commit 9511e91

Please sign in to comment.