Skip to content

Commit

Permalink
Work on Harmonic and Whistle recognizers
Browse files Browse the repository at this point in the history
Issue#281 anthony also adds in some Config file extentions.
  • Loading branch information
towsey committed Feb 5, 2020
1 parent 9511e91 commit f9d4f9d
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 167 deletions.
1 change: 1 addition & 0 deletions src/Acoustics.Shared/Acoustics.Shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@
<Compile Include="Csv\Csv.cs" />
<Compile Include="Csv\ISetPointConverter.cs" />
<Compile Include="Debugging\AutoAttachVs.cs" />
<Compile Include="Extensions\ConfigFileExtensions.cs" />
<Compile Include="Extensions\EnumExtensions.cs" />
<Compile Include="Enums.cs" />
<Compile Include="ExpressionVisitor.cs" />
Expand Down
9 changes: 8 additions & 1 deletion src/Acoustics.Shared/ConfigFile/ConfigFileException.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// --------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConfigFileException.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>
Expand All @@ -10,6 +10,7 @@
namespace Acoustics.Shared.ConfigFile
{
using System;
using System.IO;

public class ConfigFileException : Exception
{
Expand All @@ -32,6 +33,12 @@ public ConfigFileException(string message, Exception innerException, string file
this.File = file;
}

public ConfigFileException(string message, FileInfo file)
: base(message, null)
{
this.File = file.FullName;
}

public ConfigFileException()
{
}
Expand Down
6 changes: 4 additions & 2 deletions src/Acoustics.Shared/Contracts/Contract.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
// <copyright file="Contract.cs" company="QutEcoacoustics">
// <copyright file="Contract.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>

namespace Acoustics.Shared.Contracts
{
using System;
using System.Diagnostics;
using System.IO;
using Acoustics.Shared.ConfigFile;
using JetBrains.Annotations;

/// <summary>
/// This is a minimal implementation of the CodeContracts API that represents 90%
/// of our use cases. It doesn't do anything fancy and ends up being sugar for
/// standard exception throwing.
/// </summary>
public class Contract
public static class Contract
{
/// <summary>
/// Require the supplied value to be not null, otherwise throw a argument null exception.
Expand Down
26 changes: 26 additions & 0 deletions src/Acoustics.Shared/Extensions/ConfigFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// <copyright file="ConfigFileExtensions.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>

namespace Acoustics.Shared.ConfigFile
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;

public static class ConfigFileExtensions
{
[ContractAnnotation("value:null => halt")]
public static void ConfigNotNull(this object value, string name, FileInfo file, string message = "must be set in the config file")
{
if (value == null)
{
throw new ConfigFileException(name + " " + message, file);
}
}
}
}
2 changes: 1 addition & 1 deletion 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 be found within the bounds of the segment analyzed");
"Every event should be found within the bounds of the current segment. This error occurs when segmentStartOffset is not set correctly.");

// ReSharper disable CompareOfFloatsByEqualityOperator
Contract.Ensures(
Expand Down
14 changes: 7 additions & 7 deletions src/AnalysisPrograms/Recognizers/Base/CommonParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,38 @@ public abstract class CommonParameters
/// <summary>
/// Gets or sets the bottom bound of the rectangle. Units are Hertz.
/// </summary>
public int MinHertz { get; set; }
public int? MinHertz { get; set; }

/// <summary>
/// Gets or sets the the top bound of the rectangle. Units are Hertz.
/// </summary>
public int MaxHertz { get; set; }
public int? MaxHertz { get; set; }

/// <summary>
/// Gets or sets the buffer (bandwidth of silence) below the component rectangle. Units are Hertz.
/// </summary>
public int BottomHertzBuffer { get; set; }
public int? BottomHertzBuffer { get; set; }

/// <summary>
/// Gets or sets the buffer (bandwidth of silence) above the component rectangle. Units are Hertz.
/// Quite often this will be set to <value>null</value> which indicates as upper bounds variable,
/// depending on distance of the source.
/// </summary>
public int TopHertzBuffer { get; set; }
public int? TopHertzBuffer { get; set; }

/// <summary>
/// Gets or sets the minimum allowed duration of the component. Units are seconds.
/// </summary>
public double MinDuration { get; set; } = 1.0;
public double? MinDuration { get; set; } = 1.0;

/// <summary>
/// Gets or sets the maximum allowed duration of the component. Units are seconds.
/// </summary>
public double MaxDuration { get; set; } = 10.0;
public double? MaxDuration { get; set; } = 10.0;

/// <summary>
/// Gets or sets the threshold of "loudness" of a component. Units are decibels.
/// </summary>
public double DecibelThreshold { get; set; } = 6;
public double? DecibelThreshold { get; set; } = 6;
}
}
164 changes: 164 additions & 0 deletions src/AnalysisPrograms/Recognizers/Base/HarmonicParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

namespace AnalysisPrograms.Recognizers.Base
{
using System;
using System.Collections.Generic;
using Acoustics.Shared;
using AudioAnalysisTools;
using AudioAnalysisTools.StandardSpectrograms;
using TowseyLibrary;

/// <summary>
/// Parameters needed from a config file to detect the stacked harmonic components of a soundscape.
Expand All @@ -15,5 +20,164 @@ namespace AnalysisPrograms.Recognizers.Base
[YamlTypeTag(typeof(HarmonicParameters))]
public class HarmonicParameters : CommonParameters
{
public static (List<AcousticEvent>, double[]) GetSyllablesWithHarmonics(
SpectrogramStandard sonogram,
int minHz,
int maxHz,
int nyquist,
double decibelThreshold,
double minDuration,
double maxDuration,
TimeSpan segmentStartOffset)
{
// paramters to be passed
double harmonicIntensityThreshold = 0.15;
int minFormantGap = 180;
int maxFormantGap = 450;

// Event threshold - Determines FP / FN trade-off for events.
double eventThreshold = 0.2;

var sonogramData = sonogram.Data;
int frameCount = sonogramData.GetLength(0);
int binCount = sonogramData.GetLength(1);

double freqBinWidth = nyquist / (double)binCount;
int minBin = (int)Math.Round(minHz / freqBinWidth);
int maxBin = (int)Math.Round(maxHz / freqBinWidth);

// set up score arrays
var harmonicScores = new double[frameCount];
var formantGaps = new double[frameCount];

// now look for harmonics in search band using the Xcorrelation-FFT technique.
int bandWidthBins = maxBin - minBin + 1;

//the Xcorrelation-FFT technique requires number of bins to scan to be power of 2.
//assuming sr=22050 and window= 512, then freq bin width = 43.0664 and 64 fft bins span 2756 Hz above the min Hz level.
//assuming sr=22050 and window= 512, then freq bin width = 43.0664 and 128 fft bins span 5513 Hz above the min Hz level.
//assuming sr=22050 and window=1024, then freq bin width = 21.5332 and 64 fft bins span 1378 Hz above the min Hz level.
//assuming sr=22050 and window=1024, then freq bin width = 21.5332 and 128 fft bins span 2756 Hz above the min Hz level.
//assuming sr=22050 and window=1024, then freq bin width = 21.5332 and 256 fft bins span 5513 Hz above the min Hz level.

int numberOfFftBins = 2048;
while (numberOfFftBins > bandWidthBins && (minBin + numberOfFftBins) > binCount)
{
numberOfFftBins /= 2;
}

//numberOfFftBins = Math.Min(256, numberOfFftBins);
maxBin = minBin + numberOfFftBins - 1;
int maxFftHertz = (int)Math.Ceiling(maxBin * sonogram.FBinWidth);

double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, frameCount - 1, maxBin);

int minCallSpan = (int)Math.Round(minDuration * sonogram.FramesPerSecond);

//ii: DETECT HARMONICS
//#############################################################################################################################################
var results = CrossCorrelation.DetectHarmonicsInSonogramMatrix(subMatrix, decibelThreshold, minCallSpan);

double[] dBArray = results.Item1;
double[] intensity = results.Item2; //an array of periodicity scores
double[] periodicity = results.Item3;

//intensity = DataTools.filterMovingAverage(intensity, 3);
int noiseBound = (int)(100 / freqBinWidth); //ignore 0-100 hz - too much noise
double[] scoreArray = new double[intensity.Length];
for (int r = 0; r < frameCount; r++)
{
if (intensity[r] < harmonicIntensityThreshold)
{
continue;
}

//ignore locations with incorrect formant gap
double herzPeriod = periodicity[r] * freqBinWidth;
if (herzPeriod < minFormantGap || herzPeriod > maxFormantGap)
{
continue;
}

//find freq having max power and use info to adjust score.
//expect humans to have max < 1000 Hz
double[] spectrum = MatrixTools.GetRow(sonogram.Data, r);
for (int j = 0; j < noiseBound; j++)
{
spectrum[j] = 0.0;
}

int maxIndex = DataTools.GetMaxIndex(spectrum);
int freqWithMaxPower = (int)Math.Round(maxIndex * freqBinWidth);
double discount = 1.0;
if (freqWithMaxPower < 1200)
{
discount = 0.0;
}

if (intensity[r] > harmonicIntensityThreshold)
{
scoreArray[r] = intensity[r] * discount;
}

//transfer info to a hits matrix.
//var hits = new double[rowCount, colCount];
//double threshold = harmonicIntensityThreshold * 0.75; //reduced threshold for display of hits
//for (int r = 0; r < rowCount; r++)
//{
// if (scoreArray[r] < threshold)
// {
// continue;
// }

// double herzPeriod = periodicity[r] * freqBinWidth;
// for (int c = minBin; c < maxbin; c++)
// {
// //hits[r, c] = herzPeriod / (double)380; //divide by 380 to get a relativePeriod;
// hits[r, c] = (herzPeriod - minFormantgap) / maxFormantgap; //to get a relativePeriod;
// }
//}

//iii: CONVERT TO ACOUSTIC EVENTS
//double maxPossibleScore = 0.5;
//int halfCallSpan = minCallSpan / 2;
//var predictedEvents = new List<AcousticEvent>();
//for (int i = 0; i < rowCount; i++)
//{
// //assume one score position per crow call
// if (scoreArray[i] < 0.001)
// {
// continue;
// }

// double startTime = (i - halfCallSpan) / framesPerSecond;
// AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, minDuration, minHz, maxHz);
// ev.SetTimeAndFreqScales(framesPerSecond, freqBinWidth);
// ev.Score = scoreArray[i];
// ev.ScoreNormalised = ev.Score / maxPossibleScore; // normalised to the user supplied threshold

// //ev.Score_MaxPossible = maxPossibleScore;
// predictedEvents.Add(ev);
//} //for loop
} // for all time frames

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

//extract the events based on length and threshhold.
// Note: This method does NOT do prior smoothing of the score array.
var acousticEvents = AcousticEvent.ConvertScoreArray2Events(
harmonicScores,
minHz,
maxFftHertz,
sonogram.FramesPerSecond,
sonogram.FBinWidth,
decibelThreshold,
minDuration,
maxDuration,
segmentStartOffset);

return (acousticEvents, harmonicScores);
}
}
}
Loading

0 comments on commit f9d4f9d

Please sign in to comment.