From 53e9c4dcfcf64d6732f27c8a20a35e3206966234 Mon Sep 17 00:00:00 2001 From: towsey Date: Tue, 13 Oct 2020 12:55:53 +1100 Subject: [PATCH] Fix unit test for Australasian Pipit. Issue #370 Adjust test for changes to generic algorith, The recognizer is now more accurate. --- .../Towsey.AnthusNovaeseelandiae.yml | 54 ++++++++---- .../Birds/AnthusNovaeseelandiae.cs | 88 +------------------ .../Recognizers/AustralPipitTests.cs | 26 +++--- 3 files changed, 49 insertions(+), 119 deletions(-) diff --git a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.AnthusNovaeseelandiae.yml b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.AnthusNovaeseelandiae.yml index e557d3e22..c6e29a67a 100644 --- a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.AnthusNovaeseelandiae.yml +++ b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.AnthusNovaeseelandiae.yml @@ -21,37 +21,55 @@ Profiles: MinHertz: 2000 MaxHertz: 7000 MinBandwidthHertz: 500 - MaxBandwidthHertz: 5000 - DecibelThreshold: 9.0 + MaxBandwidthHertz: 6000 + DecibelThresholds: + - 6.0 + - 9.0 + - 12.0 #################### POST-PROCESSING of EVENTS ################### -# A: First post-processing steps are to combine overlapping/proximal/sequential events -# 1: Combine overlapping events -CombineOverlappingEvents: true +PostProcessing: +# The following generic post-processing steps are determined by config settings. +# Step 1: Combine overlapping events - events derived from all profiles. +# Step 2: Combine possible syllable sequences and filter on excess syllable count. +# Step 3: Remove events whose bandwidth is too small or large. +# Step 4: Remove events that have excessive noise in their side-bands. -# 2: Combine syllables that possibly belong to the same strophe. -# Can also use this to "mop up" events in neighbourhood - these can be removed later. -CombinePossibleSyllableSequence: true -SyllableStartDifference: 0.25 -SyllableHertzGap: 3000 + # 1: Combine overlapping events + CombineOverlappingEvents: true -# B: Filter the events for excess activity in their upper and lower buffer zones -NeighbourhoodLowerHertzBuffer: 200 -NeighbourhoodUpperHertzBuffer: 0 -NeighbourhoodDbThreshold: 9.0 + # 2: Combine possible syllable sequences + SyllableSequence: + CombinePossibleSyllableSequence: false + SyllableStartDifference: 0.25 + SyllableHertzGap: 3000 + FilterSyllableSequence: false + SyllableMaxCount: 0 + ExpectedPeriod: 0 -# C: Options to save results files -# 4: Available options for saving spectrograms (case-sensitive): [False/Never | True/Always | WhenEventsDetected] + # 3: Remove events whose bandwidth lies outside 3 SDs of an expected value. + Bandwidth: + ExpectedBandwidth: 4000 + BandwidthStandardDeviation: 400 + + # 4: Filter the events for excess activity in their sidebands, i.e. upper and lower buffer zones + SidebandActivity: + LowerHertzBuffer: 0 #200 + UpperHertzBuffer: 0 + DecibelBuffer: 0.0 #9.0 + +# Options to save results files +# 1: Available options for saving spectrograms (case-sensitive): [False/Never | True/Always | WhenEventsDetected] # "True" is useful when debugging but "WhenEventsDetected" is required for operational use. #SaveSonogramImages: True SaveSonogramImages: WhenEventsDetected -# 5: Available options for saving data files (case-sensitive): [False/Never | True/Always | WhenEventsDetected] +# 2: 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 +# 3: DisplayCsvImage is obsolete - ensure it remains set to: false DisplayCsvImage: false ## End section for AnalyzeLongRecording diff --git a/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs b/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs index 6ef6a49eb..b0b558134 100644 --- a/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs +++ b/src/AnalysisPrograms/Recognizers/Birds/AnthusNovaeseelandiae.cs @@ -92,97 +92,11 @@ public override RecognizerResults Recognize( outputDirectory, imageWidth); - // ################### POST-PROCESSING of EVENTS ################### - - if (combinedResults.NewEvents.Count == 0) - { - PipitLog.Debug($"Return zero events."); - return combinedResults; - } - - // 1: Filter the events for duration in seconds - var minimumEventDuration = 0.1; - var maximumEventDuration = 0.4; - combinedResults.NewEvents = EventFilters.FilterOnDuration(combinedResults.NewEvents, minimumEventDuration, maximumEventDuration); - PipitLog.Debug($"Event count after filtering on duration = {combinedResults.NewEvents.Count}"); - - // 2: Filter the events for bandwidth in Hertz - double average = 3500; - double sd = 600; - double sigmaThreshold = 3.0; - combinedResults.NewEvents = EventFilters.FilterOnBandwidth(combinedResults.NewEvents, average, sd, sigmaThreshold); - PipitLog.Debug($"Event count after filtering on bandwidth = {combinedResults.NewEvents.Count}"); - - combinedResults.NewEvents = FilterEventsOnFrequencyProfile(combinedResults.NewEvents); + // ################### YOU CAN PUT ADDITIONAL POST-PROCESSING CODE HERE ################### return combinedResults; } - /// - /// This method assumes that the only events of interest are composite events. - /// - /// THe current list of events. - /// A list of composite events. - public static List FilterEventsOnFrequencyProfile(List events) - { - if (events.Count == 0) - { - return events; - } - - // select only the composite events. - //var compositeEvents = events.Select(x => (CompositeEvent)x).ToList(); - var (compositeEvents, others) = events.FilterForEventType(); - - if (compositeEvents == null || compositeEvents.Count == 0) - { - return events; - } - - // get the composite track for each composite event. - var returnEvents = new List(); - foreach (var ev in compositeEvents) - { - var componentEvents = ev.ComponentEvents.Cast(); - var points = EventExtentions.GetCompositeTrack(componentEvents).ToArray(); - - // Uncomment this line when want to see the composite track profile. - //WriteFrequencyProfile(points); - - // For Pipit require minimum of four frames duration. - var length = points.Length; - if (length < 4) - { - continue; - } - - // Only select events having strong downward slope in spectrogram. - var avFirstTwoEvents = (points[0].Hertz.Minimum + points[0].Hertz.Minimum) / 2; - var avLastTwoEvents = (points[length - 1].Hertz.Minimum + points[length - 2].Hertz.Minimum) / 2; - if (avFirstTwoEvents - avLastTwoEvents > 500) - { - returnEvents.Add(ev); - } - } - - return returnEvents; - } - - public static void WriteFrequencyProfile(ISpectralPoint[] points) - { - if (points != null) - { - var str = $"Track({points[0].Seconds.Minimum:F2}):"; - - foreach (var point in points) - { - str += $" {point.Hertz.Minimum},"; - } - - Console.WriteLine(str); - } - } - /* /// /// Summarize your results. This method is invoked exactly once per original file. diff --git a/tests/Acoustics.Test/AnalysisPrograms/Recognizers/AustralPipitTests.cs b/tests/Acoustics.Test/AnalysisPrograms/Recognizers/AustralPipitTests.cs index dfb6bddc0..d7427911a 100644 --- a/tests/Acoustics.Test/AnalysisPrograms/Recognizers/AustralPipitTests.cs +++ b/tests/Acoustics.Test/AnalysisPrograms/Recognizers/AustralPipitTests.cs @@ -58,30 +58,28 @@ public void TestRecognizer() this.SaveTestOutput( outputDirectory => GenericRecognizer.SaveDebugSpectrogram(results, null, outputDirectory, Recognizer.SpeciesName)); - //this test returns two false-positives with the current component parameters. - Assert.AreEqual(7, events.Count); + Assert.AreEqual(5, events.Count); Assert.IsNull(scoreTrack); - Assert.AreEqual(1, plots.Count); + Assert.AreEqual(3, plots.Count); Assert.AreEqual(1874, sonogram.FrameCount); - Assert.IsInstanceOfType(events[2], typeof(CompositeEvent)); - var ev = (CompositeEvent)events[2]; - // events[2] should be a composite event. - Assert.AreEqual(16.656, ev.EventStartSeconds); - Assert.AreEqual(17.008, ev.EventEndSeconds); - Assert.AreEqual(3596, ev.BandWidthHertz); + var ev = (CompositeEvent)events[2]; + Assert.IsInstanceOfType(events[2], typeof(CompositeEvent)); + Assert.AreEqual(22.0000000000000, ev.EventStartSeconds, TestHelper.AllowedDelta); + Assert.AreEqual(22.3680000000000, ev.EventEndSeconds, TestHelper.AllowedDelta); + Assert.AreEqual(4743, ev.BandWidthHertz); - // This event should contain 5 component events + // This event should contain 13 component events var componentEvents = ev.ComponentEvents; - Assert.AreEqual(5, componentEvents.Count); + Assert.AreEqual(13, componentEvents.Count); // This tests that the component tracks are correctly combined. //This can also be tested somewhere else, starting with just the comosite event in json file. var points = EventExtentions.GetCompositeTrack(componentEvents.Cast()).ToArray(); - Assert.AreEqual(16.672, points[1].Seconds.Minimum); - Assert.AreEqual(5425, points[1].Hertz.Minimum); - Assert.AreEqual(23.712453258003087, points[1].Value, TestHelper.AllowedDelta); + Assert.AreEqual(22.0160000000000, points[1].Seconds.Minimum, TestHelper.AllowedDelta); + Assert.AreEqual(5456, points[1].Hertz.Minimum); + Assert.AreEqual(23.13758005922, points[1].Value, TestHelper.AllowedDelta); } } }