From 6481e0e15b836ebb7900d92f2fe9f9ca6c7d76d4 Mon Sep 17 00:00:00 2001 From: towsey Date: Sat, 15 Jun 2019 14:44:31 +1000 Subject: [PATCH] Set up Flying Fox Recogniser classes --- .../Towsey.PteropusSpecies.yml | 81 ++++++ src/AnalysisPrograms/AnalysisPrograms.csproj | 1 + .../Recognizers/PteropusSpecies.cs | 243 ++++++++++++++++++ src/AnalysisPrograms/Sandpit.cs | 12 +- 4 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml create mode 100644 src/AnalysisPrograms/Recognizers/PteropusSpecies.cs diff --git a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml new file mode 100644 index 000000000..8463c0a29 --- /dev/null +++ b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml @@ -0,0 +1,81 @@ +--- +# Should be in proper casing. The full real name of the species +SpeciesName: Pteropus species +CommonName: Flying Fox +# Abbreviation of species name, format how you like +AbbreviatedSpeciesName: PteropusSp + +#Proposed Approach: +#1. Assume that a long recording has been broken into one-minute segments. +#2. Convert each one-minute segment to a spectrogram. +#3. Obtain a noise profile for each segment. This is to be used later to remove insect chorusing. +#4. Scan the one-minute waveform and select "spike maxima" whose amplitude exceeds a decibel threshold, D. +#5. Extract a single frame (say 512 samples) centred on each spike and convert to a spike spectrum. +#6. Subtract the noise profile from the spike spectrum. +#7. Smooth the remaining spectrum. +#8. Look for evenly spaced harmonics in the smoothed spectrum. +# Typically the lowest harmonic will lie between 1200 Hz and 3000 Hz and the higher ones evenly spaced. +# This is the tricky bit due to variability but may work to use spectrum auto-correlation. + +# Custom settings + +SomeExampleSettingA: 3.0 + +# Each of these profiles will be analyzed +Profiles: + # The below settings are a template + Standard: &STANDARD + # min and max of the freq band to search + MinHz: 3000 + MaxHz: 5000 + # duration of DCT in seconds + DctDuration: 0.15 + # minimum acceptable value of a DCT coefficient + DctThreshold: 0.6 + # ignore oscillation rates below the min & above the max threshold + # OSCILLATIONS PER SECOND + MinOcilFreq: 50 + MaxOcilFreq: 140 + # Minimum and maximum duration for the length of a true call. + MinDuration: 0.1 + MaxDuration: 0.8 + # Event threshold - use this to determine FP / FN trade-off for events. + EventThreshold: 0.30 + # This notation means the Groote profile has all of the settings that the Standard profile has, + # however, the MinHz and MaxHz properties have been overridden. + #Groote: + # <<: *STANDARD + # MinHz: 4000 + # MaxHz: 6000 + #FemaleRelease: + # <<: *STANDARD + # DctDuration: 0.3 + +# Standard settings +DoNoiseReduction: false +#BgNoiseThreshold: 3.0 + +EventThreshold: 0.2 + +# Resample rate must be 2 X the desired Nyquist +# ResampleRate: 17640 +ResampleRate: 22050 + +## Specifically for AnalyzeLongRecording +# SegmentDuration: units=seconds; +SegmentDuration: 60 +# SegmentOverlap: units=seconds; +SegmentOverlap: 0 +# Available options (case-sensitive): [False/Never | True/Always | WhenEventsDetected] +SaveIntermediateWavFiles: Never +SaveIntermediateCsvFiles: false +# Available options (case-sensitive): [False/Never | True/Always | WhenEventsDetected] +SaveSonogramImages: Never +# DisplayCsvImage is obsolete - ensure it remains set to: false +DisplayCsvImage: false +## End section for AnalyzeLongRecording + +# Other config files to reference + +HighResolutionIndicesConfig: "../Towsey.Acoustic.HiResIndicesForRecognisers.yml" +... \ No newline at end of file diff --git a/src/AnalysisPrograms/AnalysisPrograms.csproj b/src/AnalysisPrograms/AnalysisPrograms.csproj index d2a488dac..d5d4b82fa 100644 --- a/src/AnalysisPrograms/AnalysisPrograms.csproj +++ b/src/AnalysisPrograms/AnalysisPrograms.csproj @@ -358,6 +358,7 @@ + diff --git a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs new file mode 100644 index 000000000..82e461607 --- /dev/null +++ b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// 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). +// +// +// This is a template recognizer for the Australian Flying Fox. +// Since there are several species, this project is started using only the generic name for Flying Foxes. + +// Proposed algorithm has 8 steps +// 1. Break long recordings into one-minute segments. +// 2. Convert each segment to a spectrogram. +// 3. Obtain a noise profile for each segment. This is to be used later to remove insect chorusing. +// 4. Scan the one-minute waveform and select "spike maxima" whose amplitude exceeds a decibel threshold, D. +// 5. Extract a single frame (say 512 samples) centred on each spike and convert to a spike spectrum. +// 6. Subtract the noise profile from the spike spectrum. +// 7. Smooth the remaining spectrum. +// 8. Look for evenly spaced harmonics in the smoothed spectrum. +// Typically the lowest harmonic will lie between 1200 Hz and 3000 Hz and the higher ones evenly spaced. +// This is the tricky bit due to variability but may work to use auto-correlation. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace AnalysisPrograms.Recognizers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using Acoustics.Shared.ConfigFile; + using Acoustics.Tools.Wav; + using AnalysisBase; + using AnalysisBase.ResultBases; + using AnalysisPrograms.Recognizers.Base; + using AudioAnalysisTools; + using AudioAnalysisTools.DSP; + using AudioAnalysisTools.Indices; + using AudioAnalysisTools.StandardSpectrograms; + using AudioAnalysisTools.WavTools; + using log4net; + using TowseyLibrary; + + /// + /// This is a template recognizer for species of Flying Fox, Pteropus species + /// + internal class PteropusSpecies : RecognizerBase + { + public override string Author => "Truskinger"; + + public override string SpeciesName => "PteropusSpecies"; + + public override string Description => "[STATUS DESCRIPTION] Detects acoustic events for species of Flying Fox, Pteropus species"; + + private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Summarize your results. This method is invoked exactly once per original file. + /// + public override void SummariseResults( + AnalysisSettings settings, + FileSegment inputFileSegment, + EventBase[] events, + SummaryIndexBase[] indices, + SpectralIndexBase[] spectralIndices, + AnalysisResult2[] results) + { + // No operation - do nothing. Feel free to add your own logic. + base.SummariseResults(settings, inputFileSegment, events, indices, spectralIndices, results); + } + + /// + /// Do your analysis. This method is called once per segment (typically one-minute segments). + /// + /// one minute of audio recording. + /// config file. + /// when recording starts. + /// not sure what this is. + /// where the recogniser results can be found. + /// assuming ????. + /// recogniser results. + public override RecognizerResults Recognize(AudioRecording audioRecording, Config configuration, TimeSpan segmentStartOffset, Lazy getSpectralIndexes, DirectoryInfo outputDirectory, int? imageWidth) + { + // Get a value from the config file - with a backup default + int minHz = configuration.GetIntOrNull(AnalysisKeys.MinHz) ?? 600; + + // Get a value from the config file - with no default, throw an exception if value is not present + //int maxHz = ((int?)configuration[AnalysisKeys.MaxHz]).Value; + + // Get a value from the config file - without a string accessor, as a double + double someExampleSettingA = configuration.GetDoubleOrNull("SomeExampleSettingA") ?? 0.0; + + // common properties + string speciesName = configuration[AnalysisKeys.SpeciesName] ?? ""; + string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? ""; + + /* + * Examples of using profiles + */ + + // Examples of the APIs available. You don't need all of these commands! Pick and choose. + bool hasProfiles = ConfigFile.HasProfiles(configuration); + + //Config profile = ConfigFile.GetProfile(configuration, "Groote"); + Config profile2; + + //bool success = ConfigFile.TryGetProfile(configuration, "FemaleRelease", out profile2); + //string[] profileNames = ConfigFile.GetProfileNames(configuration); + // IEnumerable<(string Name, object Profile)> allProfiles = ConfigFile.GetAllProfiles>(configuration); + // foreach (var profile in allProfiles) + // { + // object currentProfile = profile.Profile; + // Log.Info(profile.Name + ": " + ((int)currentProfile.MinHz).ToString()); + // } + + //###################### + //2.Convert each segment to a spectrogram. + + // Profile example: running the same algorithm on every profile with different settings (regional variation) + /* + List allAcousticEvents = new List(); + Dictionary allProfiles = ConfigFile.GetAllProfiles(configuration); + foreach (var kvp in allProfiles) + { + string profileName = kvp.Key; + Log.Info($"Analyzing profile: {profileName}"); + Config currentProfile = kvp.Value; + + // extract parameters + int minHz = (int)configuration[AnalysisKeys.MinHz]; + + // ... + + // run the algorithm + List acousticEvents; + Oscillations2012.Execute( All the correct parameters, minHz); + + // augment the returned events + acousticEvents.ForEach(ae => + { + ae.SpeciesName = speciesName; + ae.Profile = profileName; + ae.AnalysisIdealSegmentDuration = recordingDuration; + ae.Name = abbreviatedSpeciesName; + }); + + // add events found in this profile to the total list + allAcousticEvents.AddRange(acousticEvents); + } + */ + + // Profile example: running a different algorithm on different profiles + /* + bool hasProfiles = ConfigFile.HasProfiles(configuration); + if (hasProfiles) + { + // add resulting events from each algorithm into the combined event list + allAcousticEvents.AddRange(RunFemaleProfile(...all the arguments)); + allAcousticEvents.AddRange(RunMaleProfile(...all the arguments)); + } + + // 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}"); + + // extract parameters + int minHz = (int)configuration[AnalysisKeys.MinHz]; + + // ... + + // run the algorithm + List acousticEvents; + Oscillations2012.Execute(All the correct parameters, minHz); + + // augment the returned events + acousticEvents.ForEach(ae => + { + ae.SpeciesName = speciesName; + ae.Profile = femaleProfile; + ae.AnalysisIdealSegmentDuration = recordingDuration; + ae.Name = abbreviatedSpeciesName; + }); + + return acousticEvents; + } + */ + + // get samples + var samples = audioRecording.WavReader.Samples; + + // make a spectrogram + var config = new SonogramConfig + { + NoiseReductionType = NoiseReductionType.Standard, + NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0, + }; + var sonogram = (BaseSonogram)new SpectrogramStandard(config, audioRecording.WavReader); + + // get high resolution indices + + // when the value is accessed, the indices are calculated + var indices = getSpectralIndexes.Value; + + // check if the indices have been calculated - you shouldn't actually need this + if (getSpectralIndexes.IsValueCreated) + { + // then indices have been calculated before + } + + var foundEvents = new List(); + + // some kind of loop where you scan through the audio + + // 'find' a Flying Fox event - if you find an event, store the data in the AcousticEvent class + var anEvent = new AcousticEvent( + segmentStartOffset, + new Oblong(50, 50, 100, 100), + sonogram.NyquistFrequency, + sonogram.Configuration.FreqBinCount, + sonogram.FrameDuration, + sonogram.FrameStep, + sonogram.FrameCount) + { + Name = "Flying Fox", + }; + + foundEvents.Add(anEvent); + + return new RecognizerResults() + { + Events = foundEvents, + Hits = null, + ScoreTrack = null, + + //Plots = null, + Sonogram = sonogram, + }; + } + } +} diff --git a/src/AnalysisPrograms/Sandpit.cs b/src/AnalysisPrograms/Sandpit.cs index 4ade42513..8957e2c62 100644 --- a/src/AnalysisPrograms/Sandpit.cs +++ b/src/AnalysisPrograms/Sandpit.cs @@ -276,6 +276,11 @@ public static void Audio2CsvOverOneFile() //FOR MULTI-ANALYSER and CROWS //audio2csv "C:\SensorNetworks\WavFiles\KoalaMale\SmallTestSet\DaguilarGoldCreek1_DM420157_0000m_00s__0059m_47s_49h.mp3" "C:\SensorNetworks\Software\AudioAnalysis\AnalysisConfigFiles\Towsey.MultiAnalyser.cfg" "C:\SensorNetworks\Output\Test1" + // FLYING FOX RECORDINGS + string recordingPath = @"C:\SensorNetworks\WavFiles\TsheringDema\WBH12HOURS-D_20160403_120000.wav"; + string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\RecognizerConfigFiles\Towsey.PteropusSpecies.yml"; + string outputPath = @"C:\Ecoacoustics\Output\BradLaw\FlyingFox"; + // TSHERING DEMA BHUTAN RECORDINGS //string recordingPath = @"C:\SensorNetworks\WavFiles\TsheringDema\WBH12HOURS-D_20160403_120000.wav"; // @"Y:\Tshering\WBH_Walaytar\201505 - second deployment\Site2_Waklaytar\24Hours WBH_28032016\WBH12HOURS-D_20160403_120000.wav"; @@ -290,7 +295,6 @@ public static void Audio2CsvOverOneFile() //string recordingPath = @"G:\SensorNetworks\WavFiles\Bhutan\SecondDeployment\WBH12HOURS-N_20160403_000000.wav"; //string outputPath = @"C:\SensorNetworks\Output\TsheringDema"; //string configPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\Towsey.Acoustic.yml"; - // string configPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\RecognizerConfigFiles\Towsey.ArdeaInsignis.yml"; //MARINE @@ -326,10 +330,10 @@ public static void Audio2CsvOverOneFile() //string configPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\Towsey.Acoustic.yml"; // Test on STANDARD 24-HOUR RECORDING - string recordingPath = @"C:\Ecoacoustics\WavFiles\LizZnidersic\TasmanIsland2015_Unit2_Mez\SM304256_0+1_20151114_131652.wav"; + //string recordingPath = @"C:\Ecoacoustics\WavFiles\LizZnidersic\TasmanIsland2015_Unit2_Mez\SM304256_0+1_20151114_131652.wav"; //string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\14"; - string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\Delete"; - string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.Acoustic.yml"; + //string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\Delete"; + //string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.Acoustic.yml"; // Ivan Campos recordings //string recordingPath = @"G:\SensorNetworks\WavFiles\Ivancampos\INCIPO01_20161031_024006_898.wav";