Skip to content

Commit

Permalink
Start work on Boobook recognizer
Browse files Browse the repository at this point in the history
Issue #297 THis boobook recognizer is the first one which is species specific but extracts generic components and then does post-processing on the find primitive components.
  • Loading branch information
towsey committed May 1, 2020
1 parent 034d396 commit 66d41d0
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---

# Resample rate must be 2 X the desired Nyquist
#ResampleRate: 22050
#ResampleRate: 16000
# SegmentDuration: units=seconds;
SegmentDuration: 60
# SegmentOverlap: units=seconds;
SegmentOverlap: 0

# Each of these profiles will be analyzed
Profiles:
PeakTrackSyllable: !ForwardTrackParameters
ComponentName: RidgeTrack
SpeciesName: NinoxBoobook
FrameSize: 1024
FrameStep: 256
WindowFunction: HANNING
BgNoiseThreshold: 0.0
# min and max of the freq band to search
MinHertz: 300
MaxHertz: 1000
MinDuration: 0.15
MaxDuration: 0.5
DecibelThreshold: 6.0

#Combine each pair of Boobook syllables as one event
#CombineProximalSimilarEvents: false
CombinePossibleSyllableSequence: true
SyllableStartDifference: 0.6
SyllableHertzGap: 200

#CombineOverlappedEvents: false
# 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

# Available options (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.
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"
...
167 changes: 83 additions & 84 deletions src/AnalysisPrograms/Recognizers/Birds/NinoxBoobook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@
// 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>
/// A recognizer for the Australian Boobook Owl, /// https://en.wikipedia.org/wiki/Australian_boobook .
/// Eight subspecies of the Australian boobook are recognized,
/// with three further subspecies being reclassified as separate species in 2019 due to their distinctive calls and genetics.
/// THis recognizer has been trained on good quality calls from the Gympie recordings obtained by Yvonne Phillips.
/// The recognizer has also been run across several recordings of Boobook from NZ (recordings obtained from Stuart Parsons.
/// The NZ Boobook calls were of poor quality (distant and echo) and were 200 Hertz higher and performance was not good.
/// </summary>
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.DSP;
using AudioAnalysisTools.Events;
using AudioAnalysisTools.Events.Types;
using AudioAnalysisTools.Indices;
using AudioAnalysisTools.StandardSpectrograms;
using AudioAnalysisTools.WavTools;
using log4net;
using SixLabors.ImageSharp;
using TowseyLibrary;
using static AnalysisPrograms.Recognizers.GenericRecognizer;
using Path = System.IO.Path;

/// <summary>
/// A recognizer for the Australian Boobook Owl, /// https://en.wikipedia.org/wiki/Australian_boobook .
/// Eight subspecies of the Australian boobook are recognized,
/// with three further subspecies being reclassified as separate species in 2019 due to their distinctive calls and genetics.
/// THis recognizer has been trained on good quality calls from the Gympie recordings obtained by Yvonne Phillips.
/// The recognizer has also been run across several recordings of Boobook from NZ (recordings obtained from Stuart Parsons.
/// The NZ Boobook calls were of poor quality (distant and echo) and were 200 Hertz higher and performance was not good.
/// </summary>
internal class NinoxBoobook : RecognizerBase
{
private static readonly ILog BoobookLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Expand All @@ -40,113 +44,108 @@ internal class NinoxBoobook : RecognizerBase

public override string Description => "[ALPHA] Detects acoustic events for the Australian Boobook owl.";

/*
/// <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)
public override AnalyzerConfig ParseConfig(FileInfo file)
{
// No operation - do nothing. Feel free to add your own logic.
base.SummariseResults(settings, inputFileSegment, events, indices, spectralIndices, results);
RuntimeHelpers.RunClassConstructor(typeof(NinoxBoobookConfig).TypeHandle);
var config = ConfigFile.Deserialize<NinoxBoobookConfig>(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("NinoxBoobook 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="genericConfig">config file that contains parameters used by all profiles.</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 genericConfig, TimeSpan segmentStartOffset, Lazy<IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int? imageWidth)
public override RecognizerResults Recognize(
AudioRecording audioRecording,
Config config,
TimeSpan segmentStartOffset,
Lazy<IndexCalculateResult[]> getSpectralIndexes,
DirectoryInfo outputDirectory,
int? imageWidth)
{
//class NinoxBoobookConfig is define at bottom of this file.
var genericConfig = (NinoxBoobookConfig)config;
var recognizer = new GenericRecognizer();
var config = recognizer.ParseConfig(FileInfo file);

/*
string[] profileNames = null;
if (ConfigFile.HasProfiles(genericConfig))
{
profileNames = ConfigFile.GetProfileNames(genericConfig);
int count = profileNames.Length;
var message = $"Found {count} config profile(s): ";
foreach (string s in profileNames)
{
message = message + (s + ", ");
}
BoobookLog.Debug(message);
}
else
{
BoobookLog.Warn("No configuration profiles found.");
}
RecognizerResults combinedResults = recognizer.Recognize(
audioRecording,
genericConfig,
segmentStartOffset,
getSpectralIndexes,
outputDirectory,
imageWidth);

// DO POST-PROCESSING of EVENTS

var combinedResults = new RecognizerResults();
// Convert events to spectral events for possible combining.
// Combine overlapping events. If the dB threshold is set low, may get lots of little events.
//
var events = combinedResults.NewEvents;
var spectralEvents = events.Select(x => (SpectralEvent)x).ToList();
var newEvents = CompositeEvent.CombineOverlappingEvents(spectralEvents);

foreach (var profileName in profileNames)
if (genericConfig.CombinePossibleSyllableSequence)
{
var results = new RecognizerResults();
if (ConfigFile.TryGetProfile(genericConfig, profileName, out var profile))
{
results = recognizer.(audioRecording, genericConfig, "Territorial", segmentStartOffset);
BoobookLog.Debug("Boobook event count = " + results.Events.Count);
}
else
{
BoobookLog.Warn($"Could not access {profileName} configuration parameters");
}
// combine the results i.e. add wing-beat events to the list of territorial call events.
//NOTE: The returned territorialResults and wingbeatResults will never be null.
combinedResults.Events.AddRange(results.Events);
combinedResults.Plots.AddRange(results.Plots);
// convert events to spectral events for possible combining.
//var spectralEvents = events.Select(x => (SpectralEvent)x).ToList();
spectralEvents = newEvents.Cast<SpectralEvent>().ToList();
var startDiff = genericConfig.SyllableStartDifference;
var hertzDiff = genericConfig.SyllableHertzGap;
newEvents = CompositeEvent.CombineSimilarProximalEvents(spectralEvents, TimeSpan.FromSeconds(startDiff), (int)hertzDiff);
}

combinedResults.NewEvents = 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 <Towsey.PteropusSpecies.yml> config file.
//SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName);
*/
//GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName);
return combinedResults;
}

/*
/// <summary>
/// returns a base sonogram type from which spectrogram images are prepared.
/// Summarize your results. This method is invoked exactly once per original file.
/// </summary>
internal static BaseSonogram GetSonogram(Config configuration, AudioRecording audioRecording)
public override void SummariseResults(
AnalysisSettings settings,
FileSegment inputFileSegment,
EventBase[] events,
SummaryIndexBase[] indices,
SpectralIndexBase[] spectralIndices,
AnalysisResult2[] results)
{
var sonoConfig = new SonogramConfig
{
WindowSize = 512,
NoiseReductionType = NoiseReductionType.Standard,
NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0,
WindowOverlap = 0.0,
};

// now construct the standard decibel spectrogram WITH noise removal
// get frame parameters for the analysis
var sonogram = (BaseSonogram)new SpectrogramStandard(sonoConfig, audioRecording.WavReader);
return sonogram;
// No operation - do nothing. Feel free to add your own logic.
base.SummariseResults(settings, inputFileSegment, events, indices, spectralIndices, results);
}
*/

/// <summary>
/// THis method can be modified if want to do something non-standard with the output spectrogram.
/// </summary>
internal static void SaveDebugSpectrogram(RecognizerResults results, Config genericConfig, DirectoryInfo outputDirectory, string baseName)
/// <inheritdoc cref="NinoxBoobookConfig"/> />
public class NinoxBoobookConfig : GenericRecognizerConfig, INamedProfiles<object>
{
//var image = sonogram.GetImageFullyAnnotated("Test");
var image = SpectrogramTools.GetSonogramPlusCharts(results.Sonogram, results.Events, results.Plots, null);
image.Save(Path.Combine(outputDirectory.FullName, baseName + ".profile.png"));
//public bool CombineOverlappedEvents { get; set; }
public bool CombinePossibleSyllableSequence { get; set; } = false;

public double SyllableStartDifference { get; set; } = 0.5;

public double SyllableHertzGap { get; set; } = 200;
}
}
}
56 changes: 29 additions & 27 deletions src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,17 @@ public override AnalyzerConfig ParseConfig(FileInfo file)
RuntimeHelpers.RunClassConstructor(typeof(GenericRecognizerConfig).TypeHandle);
var result = ConfigFile.Deserialize<GenericRecognizerConfig>(file);

this.combineOverlappedEvents = result.CombineOverlappedEvents;
// validation of configs can be done here
ValidateProfileTagsMatchAlgorithms(result.Profiles, file);

return result;
}

public static void ValidateProfileTagsMatchAlgorithms(Dictionary<string, object> profiles, FileInfo file)
{
// validation of configs can be done here
// sanity check the algorithm
foreach (var (profileName, profile) in result.Profiles)
foreach (var (profileName, profile) in profiles)
{
if (profile is CommonParameters c)
{
Expand Down Expand Up @@ -103,8 +109,6 @@ public override AnalyzerConfig ParseConfig(FileInfo file)
throw new ConfigFileException($"The algorithm type in profile {profileName} is not recognized. It must be one of {allowedAlgorithms}");
}
}

return result;
}

/// <inheritdoc/>
Expand All @@ -118,7 +122,7 @@ public override RecognizerResults Recognize(
{
var configuration = (GenericRecognizerConfig)genericConfig;

if (configuration.Profiles.NotNull() && configuration.Profiles.Count == 0)
if (configuration.Profiles?.Count < 1)
{
throw new ConfigFileException(
"The generic recognizer needs at least one profile set. 0 were found.");
Expand Down Expand Up @@ -358,19 +362,14 @@ public override void SummariseResults(
}
*/

private static SonogramConfig ParametersToSonogramConfig(CommonParameters common)
/// <summary>
/// THis method can be modified if want to do something non-standard with the output spectrogram.
/// </summary>
public static void SaveDebugSpectrogram(RecognizerResults results, Config genericConfig, DirectoryInfo outputDirectory, string baseName)
{
int windowSize = (int)common.FrameSize;
int windowStep = (int)common.FrameStep;
return new SonogramConfig()
{
WindowSize = windowSize,
WindowStep = windowStep,
WindowOverlap = (windowSize - windowStep) / (double)windowSize,
WindowFunction = (string)common.WindowFunction,
NoiseReductionType = NoiseReductionType.Standard,
NoiseReductionParameter = common.BgNoiseThreshold ?? 0.0,
};
var image3 = SpectrogramTools.GetSonogramPlusCharts(results.Sonogram, results.NewEvents, results.Plots, null);

image3.Save(Path.Combine(outputDirectory.FullName, baseName + ".profile.png"));
}

/// <summary>
Expand All @@ -390,21 +389,24 @@ private static Plot PreparePlot(double[] array, string title, double threshold)
return plot;
}

/// <summary>
/// THis method can be modified if want to do something non-standard with the output spectrogram.
/// </summary>
public static void SaveDebugSpectrogram(RecognizerResults results, Config genericConfig, DirectoryInfo outputDirectory, string baseName)
private static SonogramConfig ParametersToSonogramConfig(CommonParameters common)
{
var image3 = SpectrogramTools.GetSonogramPlusCharts(results.Sonogram, results.NewEvents, results.Plots, null);

image3.Save(Path.Combine(outputDirectory.FullName, baseName + ".profile.png"));
int windowSize = (int)common.FrameSize;
int windowStep = (int)common.FrameStep;
return new SonogramConfig()
{
WindowSize = windowSize,
WindowStep = windowStep,
WindowOverlap = (windowSize - windowStep) / (double)windowSize,
WindowFunction = (string)common.WindowFunction,
NoiseReductionType = NoiseReductionType.Standard,
NoiseReductionParameter = common.BgNoiseThreshold ?? 0.0,
};
}

/// <inheritdoc cref="RecognizerConfig"/> />
/// <inheritdoc cref="GenericRecognizerConfig"/> />
public class GenericRecognizerConfig : RecognizerConfig, INamedProfiles<object>
{
public bool CombineOverlappedEvents { get; set; }

/// <inheritdoc />
public Dictionary<string, object> Profiles { get; set; }
}
Expand Down
Loading

0 comments on commit 66d41d0

Please sign in to comment.