-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Started set up of the Cisticola recognizer
Issue #321 The yml file for this recognizer has not yet been edited for parameter values. I.e. only set up the architecture.
- Loading branch information
Showing
3 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.CisticolaExilis.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
|
||
# Golden-headed cisticola = Cisticola exilis | ||
# Resample rate must be 2 X the desired Nyquist | ||
ResampleRate: 8000 | ||
# SegmentDuration: units=seconds; | ||
SegmentDuration: 60 | ||
# SegmentOverlap: units=seconds; | ||
SegmentOverlap: 0 | ||
|
||
# Each of these profiles will be analyzed | ||
# This profile is required for the species-specific recogniser and must have the current name. | ||
Profiles: | ||
CisticolaSyllable: !UpwardTrackParameters | ||
ComponentName: Whip | ||
SpeciesName: CisticolaExilis | ||
FrameSize: 512 | ||
FrameStep: 256 | ||
WindowFunction: HANNING | ||
#BgNoiseThreshold: 0.0 | ||
# min and max of the freq band to search | ||
MinHertz: 100 | ||
MaxHertz: 200 | ||
MinDuration: 0.3 | ||
MaxDuration: 1.0 | ||
DecibelThreshold: 6.0 | ||
|
||
#################### POST-PROCESSING of EVENTS ################### | ||
|
||
# A: First post-processing steps are to combine overlapping/proximal/sequential events | ||
# 1: Combine overlapping events | ||
#CombineOverlappingEvents: false | ||
|
||
# 2: Combine each pair of Boobook syllables as one event | ||
# Can also use this to "mop up" events in neighbourhood - these can be removed later. | ||
CombinePossibleSyllableSequence: true | ||
SyllableStartDifference: 3.0 | ||
SyllableHertzGap: 35 | ||
|
||
# B: Filter the events for excess activity in their upper and lower buffer zones | ||
LowerHertzBuffer: 150 | ||
UpperHertzBuffer: 400 | ||
|
||
# C: Options to save results files | ||
# 4: Available options for saving data files (case-sensitive): [False/Never | True/Always | WhenEventsDetected] | ||
SaveIntermediateWavFiles: Never | ||
SaveIntermediateCsvFiles: false | ||
# Available options (case-sensitive): [False/Never | True/Always | WhenEventsDetected] | ||
# "True" is useful when debugging but "WhenEventsDetected" is required for operational use. | ||
|
||
# 5: Available options for saving | ||
#SaveSonogramImages: True | ||
SaveSonogramImages: WhenEventsDetected | ||
# 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" | ||
|
||
################################################################################ | ||
# Common settings | ||
#Standard: &STANDARD | ||
#EventThreshold: 0.2 | ||
#BgNoiseThreshold: 3.0 | ||
|
||
# This notation means the a profile has all of the settings that the Standard profile has, | ||
# however, the DctDuration parameter has been overridden. | ||
# <<: *STANDARD | ||
# DctDuration: 0.3 | ||
... |
219 changes: 219 additions & 0 deletions
219
src/AnalysisPrograms/Recognizers/Birds/CisticolaExilis.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
// <copyright file="CisticolaExilis.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 AnalysisPrograms.Recognizers | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Runtime.CompilerServices; | ||
using Acoustics.Shared.ConfigFile; | ||
using AnalysisBase; | ||
using AnalysisPrograms.Recognizers.Base; | ||
using AudioAnalysisTools; | ||
using AudioAnalysisTools.Events; | ||
using AudioAnalysisTools.Events.Types; | ||
using AudioAnalysisTools.Indices; | ||
using AudioAnalysisTools.WavTools; | ||
using log4net; | ||
using SixLabors.ImageSharp; | ||
using TowseyLibrary; | ||
using static AnalysisPrograms.Recognizers.GenericRecognizer; | ||
using Path = System.IO.Path; | ||
|
||
/// <summary> | ||
/// A recognizer for calls of the golden-headed cisticola (Cisticola exilis), https://en.wikipedia.org/wiki/Golden-headed_cisticola . | ||
/// It is a species of warbler in the family Cisticolidae, found in Australia and 13 Asian countries. | ||
/// Grows to 9–11.5 centimetres (3.5–4.5 in) long, it is usually brown and cream in colour. | ||
/// It produces a variety of calls distinct from other birds, which, according to the Sunshine Coast Council, range from a "teewip" to a "wheezz, whit-whit". | ||
/// It has a very large range and population, which is thought to be increasing. It is not a threatened species. | ||
/// </summary> | ||
internal class CisticolaExilis : RecognizerBase | ||
{ | ||
private static readonly ILog CisticolaLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
|
||
public override string Author => "Towsey"; | ||
|
||
public override string SpeciesName => "CisticolaExilis"; | ||
|
||
public override string Description => "[ALPHA] Detects acoustic events for the golden-headed cisticola."; | ||
|
||
public override AnalyzerConfig ParseConfig(FileInfo file) | ||
{ | ||
RuntimeHelpers.RunClassConstructor(typeof(CisticolaExilisConfig).TypeHandle); | ||
var config = ConfigFile.Deserialize<CisticolaExilisConfig>(file); | ||
|
||
// validation of configs can be done here | ||
GenericRecognizer.ValidateProfileTagsMatchAlgorithms(config.Profiles, file); | ||
|
||
// This call sets a restriction so that only one generic algorithm is used. | ||
// CHANGE this to accept multiple generic algorithms as required. | ||
//if (result.Profiles.SingleOrDefault() is ForwardTrackParameters) | ||
if (config.Profiles?.Count == 1 && config.Profiles.First().Value is ForwardTrackParameters) | ||
{ | ||
return config; | ||
} | ||
|
||
throw new ConfigFileException("CisticolaExilis expects one and only one ForwardTrack algorithm.", file); | ||
} | ||
|
||
/// <summary> | ||
/// This method is called once per segment (typically one-minute segments). | ||
/// </summary> | ||
/// <param name="audioRecording">one minute of audio recording.</param> | ||
/// <param name="config">config file that contains parameters used by all profiles.</param> | ||
/// <param name="segmentStartOffset">when recording starts.</param> | ||
/// <param name="getSpectralIndexes">not sure what this is.</param> | ||
/// <param name="outputDirectory">where the recognizer results can be found.</param> | ||
/// <param name="imageWidth"> assuming ????.</param> | ||
/// <returns>recognizer results.</returns> | ||
public override RecognizerResults Recognize( | ||
AudioRecording audioRecording, | ||
Config config, | ||
TimeSpan segmentStartOffset, | ||
Lazy<IndexCalculateResult[]> getSpectralIndexes, | ||
DirectoryInfo outputDirectory, | ||
int? imageWidth) | ||
{ | ||
//class CisticolaExilisConfig is defined at bottom of this file. | ||
var genericConfig = (CisticolaExilisConfig)config; | ||
var recognizer = new GenericRecognizer(); | ||
|
||
RecognizerResults combinedResults = recognizer.Recognize( | ||
audioRecording, | ||
genericConfig, | ||
segmentStartOffset, | ||
getSpectralIndexes, | ||
outputDirectory, | ||
imageWidth); | ||
|
||
// ################### POST-PROCESSING of EVENTS ################### | ||
// Following two commented lines are different ways of casting lists. | ||
//var newEvents = spectralEvents.Cast<EventCommon>().ToList(); | ||
//var spectralEvents = events.Select(x => (SpectralEvent)x).ToList(); | ||
|
||
// 1: Pull out the chirp events and calculate their frequency profiles. | ||
var (chirpEvents, others) = combinedResults.NewEvents.FilterForEventType<ChirpEvent, EventCommon>(); | ||
|
||
if (combinedResults.NewEvents.Count == 0) | ||
{ | ||
CisticolaLog.Debug($"Return zero events."); | ||
return combinedResults; | ||
} | ||
|
||
// 2: Combine overlapping events. If the dB threshold is set low, may get lots of little events. | ||
combinedResults.NewEvents = CompositeEvent.CombineOverlappingEvents(chirpEvents.Cast<EventCommon>().ToList()); | ||
CisticolaLog.Debug($"Event count after combining overlaps = {combinedResults.NewEvents.Count}"); | ||
|
||
// 3: Combine proximal events. If the dB threshold is set low, may get lots of little events. | ||
if (genericConfig.CombinePossibleSyllableSequence) | ||
{ | ||
// Convert events to spectral events for combining of possible sequences. | ||
// Can also use this parameter to combine events that are in the upper or lower neighbourhood. | ||
// Such combinations will increase bandwidth of the event and this property can be used later to weed out unlikely events. | ||
var spectralEvents1 = combinedResults.NewEvents.Cast<SpectralEvent>().ToList(); | ||
var startDiff = genericConfig.SyllableStartDifference; | ||
var hertzDiff = genericConfig.SyllableHertzGap; | ||
combinedResults.NewEvents = CompositeEvent.CombineProximalEvents(spectralEvents1, TimeSpan.FromSeconds(startDiff), (int)hertzDiff); | ||
CisticolaLog.Debug($"Event count after combining proximals = {combinedResults.NewEvents.Count}"); | ||
} | ||
|
||
// Get the CisticolaSyllable config. | ||
const string profileName = "CisticolaSyllable"; | ||
var configuration = (CisticolaExilisConfig)genericConfig; | ||
var chirpConfig = (ForwardTrackParameters)configuration.Profiles[profileName]; | ||
|
||
// 4: Filter events on the amount of acoustic activity in their upper and lower neighbourhoods - their buffer zone. | ||
// The idea is that an unambiguous event should have some acoustic space above and below. | ||
// The filter requires that the average acoustic activity in each frame and bin of the upper and lower buffer zones should not exceed the user specified decibel threshold. | ||
// The bandwidth of these two neighbourhoods is determined by the following parameters. | ||
// ########## These parameters could be specified by user in config.yml file. | ||
var upperHertzBuffer = 400; | ||
var lowerHertzBuffer = 150; | ||
|
||
// The decibel threshold is currently set 5/6ths of the user specified threshold. | ||
// THIS IS TO BE WATCHED. IT MAY PROVE TO BE INAPPROPRIATE TO HARD-CODE. | ||
// Want the activity in buffer zones to be "somewhat" less than the user-defined threshold. | ||
var neighbourhoodDbThreshold = chirpConfig.DecibelThreshold.Value * 0.8333; | ||
|
||
if (upperHertzBuffer > 0 || lowerHertzBuffer > 0) | ||
{ | ||
var spectralEvents2 = combinedResults.NewEvents.Cast<SpectralEvent>().ToList(); | ||
combinedResults.NewEvents = EventExtentions.FilterEventsOnNeighbourhood( | ||
spectralEvents2, | ||
combinedResults.Sonogram, | ||
lowerHertzBuffer, | ||
upperHertzBuffer, | ||
segmentStartOffset, | ||
neighbourhoodDbThreshold); | ||
|
||
CisticolaLog.Debug($"Event count after filtering on neighbourhood = {combinedResults.NewEvents.Count}"); | ||
} | ||
|
||
if (combinedResults.NewEvents.Count == 0) | ||
{ | ||
CisticolaLog.Debug($"Return zero events."); | ||
return combinedResults; | ||
} | ||
|
||
// 5: Filter on COMPONENT COUNT in Composite events. | ||
int maxComponentCount = 2; | ||
combinedResults.NewEvents = EventExtentions.FilterEventsOnCompositeContent(combinedResults.NewEvents, maxComponentCount); | ||
CisticolaLog.Debug($"Event count after filtering on component count = {combinedResults.NewEvents.Count}"); | ||
|
||
// 6: Filter the events for duration in seconds | ||
var minimumEventDuration = chirpConfig.MinDuration; | ||
var maximumEventDuration = chirpConfig.MaxDuration; | ||
if (genericConfig.CombinePossibleSyllableSequence) | ||
{ | ||
minimumEventDuration *= 2.0; | ||
maximumEventDuration *= 1.5; | ||
} | ||
|
||
combinedResults.NewEvents = EventExtentions.FilterOnDuration(combinedResults.NewEvents, minimumEventDuration.Value, maximumEventDuration.Value); | ||
CisticolaLog.Debug($"Event count after filtering on duration = {combinedResults.NewEvents.Count}"); | ||
|
||
// 7: Filter the events for bandwidth in Hertz | ||
double average = 280; | ||
double sd = 40; | ||
double sigmaThreshold = 3.0; | ||
combinedResults.NewEvents = EventExtentions.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold); | ||
CisticolaLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}"); | ||
|
||
//UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots. | ||
// NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in UserName.SpeciesName.yml config file. | ||
//GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName); | ||
return combinedResults; | ||
} | ||
|
||
/* | ||
/// <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); | ||
} | ||
*/ | ||
|
||
/// <inheritdoc cref="CisticolaExilisConfig"/> /> | ||
public class CisticolaExilisConfig : GenericRecognizerConfig, INamedProfiles<object> | ||
{ | ||
public bool CombinePossibleSyllableSequence { get; set; } = false; | ||
|
||
public double SyllableStartDifference { get; set; } = 0.5; | ||
|
||
public double SyllableHertzGap { get; set; } = 200; | ||
} | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
tests/Acoustics.Test/AnalysisPrograms/Recognizers/CisticolaTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// <copyright file="CisticolaTests.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.Test.AnalysisPrograms.Recognizers | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using Acoustics.Test.TestHelpers; | ||
using Acoustics.Tools.Wav; | ||
using global::AnalysisPrograms.Recognizers; | ||
using global::AnalysisPrograms.SourcePreparers; | ||
using global::AudioAnalysisTools.Events; | ||
using global::AudioAnalysisTools.Events.Types; | ||
using global::AudioAnalysisTools.WavTools; | ||
using global::TowseyLibrary; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
/// <summary> | ||
/// Species name = Golden-headed cisticola = Cisticola exilis. | ||
/// </summary> | ||
[TestClass] | ||
public class CisticolaTests : OutputDirectoryTest | ||
{ | ||
/// <summary> | ||
/// The canonical recording used for this recognizer is a 31 second recording . | ||
/// </summary> | ||
private static readonly FileInfo TestAsset = PathHelper.ResolveAsset("Recordings", "gympie_np_1192_331618_20150818_054959_31_0.wav"); | ||
private static readonly FileInfo ConfigFile = PathHelper.ResolveConfigFile("RecognizerConfigFiles", "Towsey.CisticolaExilis.yml"); | ||
private static readonly AudioRecording Recording = new AudioRecording(TestAsset); | ||
private static readonly CisticolaExilis Recognizer = new CisticolaExilis(); | ||
|
||
[TestMethod] | ||
public void TestRecognizer() | ||
{ | ||
var config = Recognizer.ParseConfig(ConfigFile); | ||
|
||
var results = Recognizer.Recognize( | ||
audioRecording: Recording, | ||
config: config, | ||
segmentStartOffset: TimeSpan.Zero, | ||
getSpectralIndexes: null, | ||
outputDirectory: this.TestOutputDirectory, | ||
imageWidth: null); | ||
|
||
var events = results.NewEvents; | ||
var scoreTrack = results.ScoreTrack; | ||
var plots = results.Plots; | ||
var sonogram = results.Sonogram; | ||
|
||
this.SaveTestOutput( | ||
outputDirectory => GenericRecognizer.SaveDebugSpectrogram(results, null, outputDirectory, Recognizer.SpeciesName)); | ||
|
||
Assert.AreEqual(8, events.Count); | ||
Assert.IsNull(scoreTrack); | ||
Assert.AreEqual(1, plots.Count); | ||
Assert.AreEqual(2667, sonogram.FrameCount); | ||
|
||
Assert.IsInstanceOfType(events[1], typeof(CompositeEvent)); | ||
|
||
var secondEvent = (CompositeEvent)events[1]; | ||
|
||
Assert.AreEqual(5.375419501133787, secondEvent.EventStartSeconds); | ||
Assert.AreEqual(6.0720181405895692, secondEvent.EventEndSeconds); | ||
Assert.AreEqual(483, secondEvent.LowFrequencyHertz); | ||
Assert.AreEqual(735, secondEvent.HighFrequencyHertz); | ||
Assert.AreEqual(20.901882476071698, secondEvent.Score, TestHelper.AllowedDelta); | ||
Assert.AreEqual(0.20786700431266195, secondEvent.ScoreNormalized, TestHelper.AllowedDelta); | ||
} | ||
} | ||
} |