Skip to content

Commit

Permalink
Penultimate work on FF recogniser
Browse files Browse the repository at this point in the history
Issue #238  This recogniser now works well on a ten minute recording containing 14-16 FF calls.
TODO: The search parameters need to be read from config file. At moment hard coded.
  • Loading branch information
towsey authored and atruskie committed Aug 28, 2019
1 parent f74098d commit 2cf3847
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 57 deletions.
126 changes: 71 additions & 55 deletions src/AnalysisPrograms/Recognizers/PteropusSpecies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ namespace AnalysisPrograms.Recognizers
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Acoustics.Shared;
using Acoustics.Shared.ConfigFile;
using Acoustics.Tools.Wav;
using AnalysisBase;
using AnalysisBase.ResultBases;
using AnalysisPrograms.Recognizers.Base;
Expand Down Expand Up @@ -100,20 +98,20 @@ internal static RecognizerResults Gruntwork(AudioRecording audioRecording, Confi
// get the common properties
string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "Pteropus species";
string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "Pteropus";

// The following parameters worked well on a ten minute recording containing 14-16 calls.
// Note: if you lower the dB threshold, you need to increase maxDurationSeconds
int minHz = configuration.GetIntOrNull(AnalysisKeys.MinHz) ?? 800;
int maxHz = configuration.GetIntOrNull(AnalysisKeys.MaxHz) ?? 8000;

//var samples = audioRecording.WavReader.Samples;
double minDurationSeconds = configuration.GetIntOrNull(AnalysisKeys.MinDuration) ?? 0.2;
double minDurationSeconds = configuration.GetIntOrNull(AnalysisKeys.MinDuration) ?? 0.15;
double maxDurationSeconds = configuration.GetIntOrNull(AnalysisKeys.MaxDuration) ?? 0.5;
var minTimeSpan = TimeSpan.FromSeconds(minDurationSeconds);
var maxTimeSpan = TimeSpan.FromSeconds(maxDurationSeconds);

double decibelThreshold = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 9.0;

//######################
//2.Convert each segment to a spectrogram.
//double noiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.1;
//2.Convert each segment to a spectrogram. Don't use samples in this recogniser.
//var samples = audioRecording.WavReader.Samples;

// make a spectrogram
var sonoConfig = new SonogramConfig
Expand Down Expand Up @@ -158,17 +156,18 @@ internal static RecognizerResults Gruntwork(AudioRecording audioRecording, Confi
ae.Name = abbreviatedSpeciesName;
});

acousticEvents = FilterEventsForSpectralProfile(acousticEvents, sonogram.Data);

//var sonoImage = sonogram.GetImageFullyAnnotated("Test");
//var sonoImage = SpectrogramTools.GetSonogramPlusCharts(sonogram, acousticEvents, plots, null);
acousticEvents = FilterEventsForSpectralProfile(acousticEvents, sonogram);

//var opPath =
// outputDirectory.Combine(
// FilenameHelpers.AnalysisResultName(
// Path.GetFileNameWithoutExtension(recording.BaseName), speciesName, "png", "DebugSpectrogram"));
//string imageFilename = audioRecording.BaseName + ".png";
//sonoImage.Save(Path.Combine(outputDirectory.FullName, imageFilename));
//Set following true if you want special debug spectrogram, i.e. with special plots
//In addition, standard spectrograms are produced when you set true in the config file, Towsey.PteropusSpecies.yml.
if (false)
{
//var image = sonogram.GetImageFullyAnnotated("Test");
var image = SpectrogramTools.GetSonogramPlusCharts(sonogram, acousticEvents, plots, null);
var opPath = outputDirectory.Combine(FilenameHelpers.AnalysisResultName(Path.GetFileNameWithoutExtension(audioRecording.BaseName), speciesName, "png", "DebugSpectrogram"));
string imageFilename = audioRecording.BaseName + ".png";
image.Save(Path.Combine(outputDirectory.FullName, imageFilename));
}

return new RecognizerResults()
{
Expand All @@ -184,61 +183,78 @@ internal static RecognizerResults Gruntwork(AudioRecording audioRecording, Confi
/// Remove events whose acoustic profile does not match that of a flying fox.
/// </summary>
/// <param name="events">unfiltered acoustic events.</param>
/// <param name="spectrogramData">matrix of spectrogram values</param>
/// <param name="sonogram">includes matrix of spectrogram values.</param>
/// <returns>filtered acoustic events.</returns>
private static List<AcousticEvent> FilterEventsForSpectralProfile(List<AcousticEvent> events, double[,] spectrogramData)
private static List<AcousticEvent> FilterEventsForSpectralProfile(List<AcousticEvent> events, BaseSonogram sonogram)
{
double[,] spectrogramData = sonogram.Data;
int colCount = spectrogramData.GetLength(1);
var filteredEvents = new List<AcousticEvent>();
foreach (AcousticEvent ae in events)
{
int startFrame = ae.Oblong.RowTop;
int endFrame = ae.Oblong.RowBottom;
var subMatrix = DataTools.Submatrix(spectrogramData, startFrame, 0, endFrame, colCount - 1);

int maxBin = (int)Math.Round(8000 / sonogram.FBinWidth);

// get all the frames of the acoustic event
//var subMatrix = DataTools.Submatrix(spectrogramData, startFrame, 0, endFrame, colCount - 1);

// get only the frames from centre of the acoustic event
var subMatrix = DataTools.Submatrix(spectrogramData, startFrame + 1, 0, startFrame + 4, maxBin);

var spectrum = MatrixTools.GetColumnAverages(subMatrix);
var normalisedSpectrum = DataTools.normalise(spectrum);
normalisedSpectrum = DataTools.filterMovingAverageOLD(normalisedSpectrum, 11);
var maxId = DataTools.GetMaxIndex(normalisedSpectrum);
var maxHz = (int)Math.Ceiling(maxId * sonogram.FBinWidth);

// do test to determine if event has spectrum matching a Flying fox.
// TODO write method to determine similarity of spectrum to a true flying fox spectrum.
// There should be little energy in 0-600 Hz band.
// There should three peaks at around 1.5 kHz, 3 kHz and 6 kHz.
bool goodMatch = true;
if (goodMatch)
{
filteredEvents.Add(ae);
}
}
// Do TESTS to determine if event has spectrum matching a Flying fox.

return filteredEvents;
}
// Test 1: Spectral maximum should be below 4 kHz.
int fourkHzBin = (int)Math.Round(4000 / sonogram.FBinWidth);
bool passTest1 = maxId < fourkHzBin;

/*
// example method
private static List<AcousticEvent> RunFemaleProfile(configuration, rest of arguments)
{
const string femaleProfile = "Female";
Config currentProfile = ConfigFile.GetProfile(configuration, femaleProfile);
Log.Info($"Analyzing profile: {femaleProfile}");
// Test 2: There should be little energy in 0-1 kHz band.
int onekHzBin = (int)Math.Round(1000 / sonogram.FBinWidth);
var subband1Khz = DataTools.Subarray(normalisedSpectrum, 0, onekHzBin);
double bandArea1 = subband1Khz.Sum();
double energyRatio1 = bandArea1 / normalisedSpectrum.Sum();

// extract parameters
int minHz = (int)configuration[AnalysisKeys.MinHz];
// 0.125 = 1/8. i.e. test requires that energy in 0-1kHz band is less than average in all 8 kHz bands
// 0.0938 = 3/32. i.e. test requires that energy in 0-1kHz band is less than 3/4 average in all 8 kHz bands
// 0.0625 = 1/16. i.e. test requires that energy in 0-1kHz band is less than half average in all 8 kHz bands
bool passTest2 = !(energyRatio1 > 0.0938);

// ...
// Test 3: There should be little energy in 4-5 kHz band.
var subband4Khz = DataTools.Subarray(normalisedSpectrum, fourkHzBin, onekHzBin);
double bandArea2 = subband4Khz.Sum();
double energyRatio2 = bandArea2 / normalisedSpectrum.Sum();
bool passTest3 = !(energyRatio2 > 0.125);

// run the algorithm
List<AcousticEvent> acousticEvents;
Oscillations2012.Execute(All the correct parameters, minHz);
// TODO write method to determine similarity of spectrum to a true flying fox spectrum.
// Problem: it is not certain how variable the FF spectra are.
// In ten minutes of recording used so far, which include 14-15 obvious calls, there appear to be two spectral types.
// One type has three peaks at around 1.5 kHz, 3 kHz and 6 kHz.
// The other type have two peaks around 2.5 and 5.5 kHz.

// augment the returned events
acousticEvents.ForEach(ae =>
//if (passTest1)
//if (true)
if (passTest1 && passTest2 && passTest3)
{
ae.SpeciesName = speciesName;
ae.Profile = femaleProfile;
ae.AnalysisIdealSegmentDuration = recordingDuration;
ae.Name = abbreviatedSpeciesName;
});
filteredEvents.Add(ae);

return acousticEvents;
// draw DEBUG IMAGES
if (true)
{
string name = "FF spectrum " + ae.SegmentStartSeconds + "s Frame" + startFrame + " maxHz" + maxHz;
var bmp2 = GraphsAndCharts.DrawGraph(name, normalisedSpectrum, 100);
bmp2.Save(Path.Combine(@"C:\Ecoacoustics\Output\BradLaw\FlyingFox\Towsey.PteropusSpecies", name + ".png"));
}
}
}
*/

return filteredEvents;
}
}
}
9 changes: 7 additions & 2 deletions src/AudioAnalysisTools/AcousticEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,10 +1172,15 @@ public static List<AcousticEvent> GetEventsAroundMaxima(
// find start frame of current event
while (values[i] > thresholdValue)
{
if (i <= 0)
{
break;
}

i--;
}

startFrame = i;
startFrame = i + 1;

// find end frame of current event
i = maxFrame;
Expand All @@ -1186,7 +1191,7 @@ public static List<AcousticEvent> GetEventsAroundMaxima(

endFrame = i;

int frameDuration = endFrame - startFrame; // +1 ?????????????????
int frameDuration = endFrame - startFrame + 1;
if (frameDuration >= minFrames && frameDuration <= maxFrames)
{
double startTime = startFrame * frameOffset; // time in seconds
Expand Down

0 comments on commit 2cf3847

Please sign in to comment.