Skip to content

Commit

Permalink
Set up the Cisticola recognizer
Browse files Browse the repository at this point in the history
Issue #321 Establish rewasonable parameters for the generic component and establish sequence of filters with their parameters.
THis recognizer has some 200 FPs on the noise data set. THis is high but will have to be whittled down using the content of the events.
  • Loading branch information
towsey committed Jun 10, 2020
1 parent b533b4a commit 44e1d6a
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Golden-headed cisticola = Cisticola exilis
# Resample rate must be 2 X the desired Nyquist
ResampleRate: 8000
ResampleRate: 16000
# SegmentDuration: units=seconds;
SegmentDuration: 60
# SegmentOverlap: units=seconds;
Expand All @@ -15,43 +15,47 @@ Profiles:
ComponentName: Whip
SpeciesName: CisticolaExilis
FrameSize: 512
FrameStep: 256
FrameStep: 128
WindowFunction: HANNING
#BgNoiseThreshold: 0.0
# min and max of the freq band to search
MinHertz: 100
MaxHertz: 200
MinDuration: 0.3
MaxDuration: 1.0
MinHertz: 2100
MaxHertz: 3300
MinBandwidthHertz: 200
MaxBandwidthHertz: 900
#MinDuration: 0.03
#MaxDuration: 0.06
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
CombineOverlappingEvents: true

# 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
SyllableStartDifference: 0.15
SyllableHertzGap: 500

# B: Filter the events for excess activity in their upper and lower buffer zones
LowerHertzBuffer: 150
UpperHertzBuffer: 400
NeighbourhoodLowerHertzBuffer: 100
NeighbourhoodUpperHertzBuffer: 200
NeighbourhoodDbThreshold: 4.0


# 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]
# 4: Available options for saving spectrograms (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

# 5: Available options for saving data files (case-sensitive): [False/Never | True/Always | WhenEventsDetected]
SaveIntermediateWavFiles: Never
SaveIntermediateCsvFiles: false

# 6: DisplayCsvImage is obsolete - ensure it remains set to: false
DisplayCsvImage: false
## End section for AnalyzeLongRecording

Expand Down
99 changes: 16 additions & 83 deletions src/AnalysisPrograms/Recognizers/Birds/CisticolaExilis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ public override AnalyzerConfig ParseConfig(FileInfo 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)
if (config.Profiles?.Count == 1 && config.Profiles.First().Value is UpwardTrackParameters)
{
return config;
}

throw new ConfigFileException("CisticolaExilis expects one and only one ForwardTrack algorithm.", file);
throw new ConfigFileException("CisticolaExilis expects one and only one UpwardTrack algorithm.", file);
}

/// <summary>
Expand Down Expand Up @@ -95,94 +95,32 @@ public override RecognizerResults Recognize(
//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);
// 1: Filter the events for duration in seconds
var minimumEventDuration = 0.1;
var maximumEventDuration = 0.25;
combinedResults.NewEvents = EventExtentions.FilterOnDuration(combinedResults.NewEvents, minimumEventDuration, maximumEventDuration);
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;
// 2: Filter the events for bandwidth in Hertz
double average = 600;
double sd = 150;
double sigmaThreshold = 3.0;
combinedResults.NewEvents = EventExtentions.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold);
CisticolaLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}");

// 3: 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}");

//combinedResults.NewEvents = FilterEventsOnFrequencyProfile(combinedResults.NewEvents);

//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);
Expand All @@ -209,11 +147,6 @@ public override void SummariseResults(
/// <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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,32 @@ namespace Acoustics.Test.AnalysisPrograms.Recognizers
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// Species name = Golden-headed cisticola = Cisticola exilis.
/// The canonical recording used for this recognizer is a 30 second recording containing five Pipit calls and a number of Cisticola and other bird calls.
/// It was recorded in Narrabri region and forms part of the Cotton Project data set.
/// </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 TestAsset = PathHelper.ResolveAsset("Recordings", "ms1_2559_630118_20170402_075841_30_0.wav");
private static readonly FileInfo TestAsset = new FileInfo(@"C:\Ecoacoustics\WavFiles\TestNoiseRecordings\Cisticola\ms1_2559_628362_20170408_074841_30_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);
int resampleRate = config.ResampleRate.Value;
string opDir = this.TestOutputDirectory.FullName;
string opFileName = "tempFile.wav";
var recording = AudioRecording.GetAudioRecording(TestAsset, resampleRate, opDir, opFileName);

var results = Recognizer.Recognize(
audioRecording: Recording,
audioRecording: recording,
config: config,
segmentStartOffset: TimeSpan.Zero,
getSpectralIndexes: null,
Expand Down

0 comments on commit 44e1d6a

Please sign in to comment.