diff --git a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs index 20a608ca5..c85823e38 100644 --- a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs +++ b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs @@ -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; @@ -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 @@ -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() { @@ -184,61 +183,78 @@ internal static RecognizerResults Gruntwork(AudioRecording audioRecording, Confi /// Remove events whose acoustic profile does not match that of a flying fox. /// /// unfiltered acoustic events. - /// matrix of spectrogram values + /// includes matrix of spectrogram values. /// filtered acoustic events. - private static List FilterEventsForSpectralProfile(List events, double[,] spectrogramData) + private static List FilterEventsForSpectralProfile(List events, BaseSonogram sonogram) { + double[,] spectrogramData = sonogram.Data; int colCount = spectrogramData.GetLength(1); var filteredEvents = new List(); 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 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 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; + } } } diff --git a/src/AudioAnalysisTools/AcousticEvent.cs b/src/AudioAnalysisTools/AcousticEvent.cs index 1c9c15252..02e51195f 100644 --- a/src/AudioAnalysisTools/AcousticEvent.cs +++ b/src/AudioAnalysisTools/AcousticEvent.cs @@ -1172,10 +1172,15 @@ public static List 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; @@ -1186,7 +1191,7 @@ public static List 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