Skip to content

Commit

Permalink
Set up Flying Fox Recogniser classes
Browse files Browse the repository at this point in the history
  • Loading branch information
towsey authored and atruskie committed Aug 28, 2019
1 parent 4bb818c commit 6481e0e
Show file tree
Hide file tree
Showing 4 changed files with 333 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -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"
...
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\LitoriaWatjulumensis.cs" />
<Compile Include="Recognizers\LitoriaBicolor.cs" />
<Compile Include="Recognizers\PezoporusOccidentalis.cs" />
<Compile Include="Recognizers\PteropusSpecies.cs" />
<Compile Include="Recognizers\UperoleiaLithomoda.cs" />
<Compile Include="Recognizers\PlatyplectrumOrnatum.cs" />
<Compile Include="Recognizers\LimnodynastesConvex.cs" />
Expand Down
243 changes: 243 additions & 0 deletions src/AnalysisPrograms/Recognizers/PteropusSpecies.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PteropusSpecies.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>
// 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.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

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;

/// <summary>
/// This is a template recognizer for species of Flying Fox, Pteropus species
/// </summary>
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);

/// <summary>
/// Summarize your results. This method is invoked exactly once per original file.
/// </summary>
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);
}

/// <summary>
/// Do your analysis. This method is called once per segment (typically one-minute segments).
/// </summary>
/// <param name="audioRecording">one minute of audio recording.</param>
/// <param name="configuration">config file.</param>
/// <param name="segmentStartOffset">when recording starts.</param>
/// <param name="getSpectralIndexes">not sure what this is.</param>
/// <param name="outputDirectory">where the recogniser results can be found.</param>
/// <param name="imageWidth"> assuming ????.</param>
/// <returns>recogniser results.</returns>
public override RecognizerResults Recognize(AudioRecording audioRecording, Config configuration, TimeSpan segmentStartOffset, Lazy<IndexCalculateResult[]> 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] ?? "<no species>";
string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>";

/*
* 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<Config, Aed.AedConfiguration>(configuration, "Groote");
Config profile2;

//bool success = ConfigFile.TryGetProfile(configuration, "FemaleRelease", out profile2);
//string[] profileNames = ConfigFile.GetProfileNames<Config>(configuration);
// IEnumerable<(string Name, object Profile)> allProfiles = ConfigFile.GetAllProfiles<IProfile<object>>(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<AcousticEvent> allAcousticEvents = new List<AcousticEvent>();
Dictionary<string, Config> 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<AcousticEvent> 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<AcousticEvent> 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<AcousticEvent> 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<AcousticEvent>();

// 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,
};
}
}
}
12 changes: 8 additions & 4 deletions src/AnalysisPrograms/Sandpit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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";
Expand Down

0 comments on commit 6481e0e

Please sign in to comment.