From 45aeb8bee783731095a6b429fce3bb8f53c062e9 Mon Sep 17 00:00:00 2001 From: towsey Date: Fri, 13 Sep 2019 18:01:47 +1000 Subject: [PATCH] Set up two new content types, Morning Chorus and Silver Eye Issue #252 Set up two new conetent type classes and refactor other classes --- src/AnalysisPrograms/Sandpit.cs | 29 ++-- .../AudioAnalysisTools.csproj | 3 +- .../ContentDescription.cs | 144 ++++++++++++++++-- .../ContentTypes/BaseContentType.cs | 78 ++++++++-- .../ContentTypes/BirdMorningChorus1.cs | 79 ++++++++++ .../ContentTypes/RainHeavy2.cs | 62 -------- .../ContentTypes/RainLight1.cs | 30 ++-- .../ContentTypes/SilverEyeMezTasmanIs.cs | 91 +++++++++++ .../ContentTypes/WindLight1.cs | 28 +++- .../ContentTypes/WindStrong1.cs | 30 ++-- 10 files changed, 453 insertions(+), 121 deletions(-) create mode 100644 src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BirdMorningChorus1.cs delete mode 100644 src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainHeavy2.cs create mode 100644 src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/SilverEyeMezTasmanIs.cs diff --git a/src/AnalysisPrograms/Sandpit.cs b/src/AnalysisPrograms/Sandpit.cs index cc6d932fb..0b854686d 100644 --- a/src/AnalysisPrograms/Sandpit.cs +++ b/src/AnalysisPrograms/Sandpit.cs @@ -177,9 +177,8 @@ public static void ContentDescriptionDev() //PREPARE STRONG WIND TEMPLATE //var ipDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez02\Towsey.Acoustic"); - //string baseName = "SM304256_0+1_20151114_011652"; - //var opDir = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "StrongWindTemplate1.csv"); - //WindStrong1.WriteStrongWindTemplateToFile(dictionary, path2); + //var opDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription"); + //WindStrong1.WriteTemplateToFile(ipDir, opDir); //PREPARE LIGHT WIND TEMPLATE //var ipDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez03\Towsey.Acoustic"); @@ -188,10 +187,18 @@ public static void ContentDescriptionDev() //PREPARE LIGHT RAIN TEMPLATE //var ipDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez08\Towsey.Acoustic"); - //string baseName = "SM304256_0+1_20151114_071652"; - //var dictionary = ContentDescription.ReadIndexMatrices(dir, baseName); - //var opDir = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "LightRainTemplate1.csv"); - //RainContent.WriteLightRainTemplateToFile(dictionary, path2); + //var opDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription"); + //WindLight1.WriteTemplateToFile(ipDir, opDir); + + //PREPARE BIRD MORNING CHORUS1 TEMPLATE + //var ipDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez05\Towsey.Acoustic"); + //var opDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription"); + //BirdMorningChorus1.WriteTemplateToFile(ipDir, opDir); + + //PREPARE MEZZANINE-TASMAN ISLAND SILVER-EYE TEMPLATE + //var ipDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez08\Towsey.Acoustic"); + //var opDir = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription"); + //SilverEyeMezTasmanIs.WriteTemplateToFile(ipDir, opDir); var contentPlots = ContentDescription.ContentDescriptionOfMultipleRecordingFiles(directories, baseNames); @@ -199,13 +206,9 @@ public static void ContentDescriptionDev() var path = Path.Combine(@"C:\Ecoacoustics\Output\Test\Test24HourRecording", "Testing__2Maps.png"); var ldfcSpectrogram = Image.FromFile(path); - //var contentScores = new List - //{ - // ContentDescription.GetRandomNumberArray(ldfcSpectrogram.Width), - //}; - + //Write image + contentPlots to file. var image = ContentVisualization.DrawLdfcSpectrogramWithContentScoreTracks(ldfcSpectrogram, contentPlots); - var path1 = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "Testing__2Maps.CONTENT4.png"); + var path1 = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "Testing_2Maps.CONTENT9.png"); image.Save(path1); } diff --git a/src/AudioAnalysisTools/AudioAnalysisTools.csproj b/src/AudioAnalysisTools/AudioAnalysisTools.csproj index d67c1b16b..6001f76e3 100644 --- a/src/AudioAnalysisTools/AudioAnalysisTools.csproj +++ b/src/AudioAnalysisTools/AudioAnalysisTools.csproj @@ -244,7 +244,8 @@ - + + diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs index efd3bfba3..fde3b47be 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs @@ -6,20 +6,19 @@ namespace AudioAnalysisTools.ContentDescriptionTools { using System; using System.Collections.Generic; - using System.Drawing; - using System.Drawing.Drawing2D; using System.IO; using System.Linq; - using Acoustics.Shared; using Acoustics.Shared.Csv; using AudioAnalysisTools.ContentDescriptionTools.ContentTypes; using AudioAnalysisTools.DSP; - using AudioAnalysisTools.LongDurationSpectrograms; - using AudioAnalysisTools.StandardSpectrograms; using TowseyLibrary; public class ContentDescription { + // All the code base for content description assumes a sampling rate of 22050 (i.e. a Nyquist = 11025) and frame size = 512 (i.e. 256 frequency bins). + public const int Nyquist = 11025; + public const int FreqBinCount = 256; + /// /// The following min and max bounds are same as those defined in the IndexPropertiesConfig.yml file as of August 2019. /// @@ -44,20 +43,22 @@ public static List ContentDescriptionOfMultipleRecordingFiles(DirectoryInf for (int i = 0; i < directories.Length; i++) { // read the spectral indices for the current file - var dictionary = ContentDescription.ReadIndexMatrices(directories[i], baseNames[i]); + var dictionary = ReadIndexMatrices(directories[i], baseNames[i]); // Draw the index matrices for check/debug purposes // var dir1 = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription"); // ContentDescription.DrawNormalisedIndexMatrices(dir1, baseName, dictionary); // get the rows and do something with them one by one. - var results = ContentDescription.AnalyseMinutes(dictionary, i * 60); // WARNING: HACK: ASSUME ONE HOUR FILES + var results = AnalyseMinutes(dictionary, i * 60); // WARNING: HACK: ASSUME ONE HOUR FILES completeListOfResults.AddRange(results); } - var plotDict = ContentDescription.ConvertResultsToPlots(completeListOfResults, 1440, 0); - var contentPlots = ContentDescription.ConvertPlotDictionaryToPlotList(plotDict); + var plotDict = ConvertResultsToPlots(completeListOfResults, 1440, 0); + var contentPlots = ConvertPlotDictionaryToPlotList(plotDict); contentPlots = SubtractMeanPlusSd(contentPlots); + //the following did not work as well. + //contentPlots = SubtractModeAndSd(contentPlots); return contentPlots; } @@ -176,6 +177,8 @@ public static List AnalyseMinutes(Dictionary ReduceIndicesByFactor(Dictionary + /// Returns the bin bounds assuming that the full spectrum consists of the defaul value = 256. + /// + /// Units = Hertz. + /// Hertz. + public static int[] GetFreqBinBounds(int bottomFrequency, int topFrequency) => GetFreqBinBounds(bottomFrequency, topFrequency, FreqBinCount); + + public static int[] GetFreqBinBounds(int bottomFrequency, int topFrequency, int binCount) + { + double binWidth = Nyquist / (double)binCount; + int bottomBin = (int)Math.Floor(bottomFrequency / binWidth); + int topBin = (int)Math.Ceiling(topFrequency / binWidth); + return new[] { bottomBin, topBin }; + } + + public static Dictionary ApplyBandPass(Dictionary indices, int bottomBin, int topBin) + { + int length = topBin - bottomBin + 1; + var opIndices = new Dictionary(); + + var keys = indices.Keys; + foreach (string key in keys) + { + var success = indices.TryGetValue(key, out double[] vector); + if (success) + { + var opVector = DataTools.Subarray(vector, bottomBin, length); + opIndices.Add(key, opVector); + } + } + + return opIndices; + } + + /// + /// THis method assumes that the passed temp[late contains only one value for each key. + /// + /// Each kvp = string, double. + /// the indices. + /// A spectrum of similarity-distance scores. + public static double[] ScanSpectrumWithTemplate(Dictionary templateDict, Dictionary oneMinuteIndices) + { + int templateLength = templateDict.First().Value.Length; + if (templateLength != 1) + { + // Abandon ship! + } + + int spectrumLength = oneMinuteIndices.First().Value.Length; + var templateVector = ConvertDictionaryToVector(templateDict); + + // the score spectrum to be returned + var spectralScores = new double[spectrumLength]; + + // scan the spectrum of indices + for (int i = 0; i < spectrumLength; i++) + { + var binVector = GetFreqBinVector(oneMinuteIndices, i); + var distance = DataTools.EuclidianDistance(templateVector, binVector); + distance /= Math.Sqrt(templateVector.Length); + spectralScores[i] = 1 - distance; + } + + return spectralScores; + } + public static double[] ConvertDictionaryToVector(Dictionary dictionary) { var list = new List(); @@ -262,6 +331,22 @@ public static double[] ConvertDictionaryToVector(Dictionary di return list.ToArray(); } + public static double[] GetFreqBinVector(Dictionary dictionary, int id) + { + var list = new List(); + var keys = dictionary.Keys; + foreach (string key in keys) + { + var success = dictionary.TryGetValue(key, out double[] indices); + if (success) + { + list.Add(indices[id]); + } + } + + return list.ToArray(); + } + public static Dictionary ConvertResultsToPlots(List results, int plotLength, int plotStart) { var plots = new Dictionary(); @@ -324,6 +409,47 @@ public static List SubtractMeanPlusSd(List plots) return opPlots; } + /// + /// THis method normalises a score array by subtracting the mode rather than the average of the array. + /// THis is because the noise is often not normally distributed but rather skewed. + /// + public static List SubtractModeAndSd(List plots) + { + var opPlots = new List(); + + // subtract average from each plot array + foreach (Plot plot in plots) + { + var scores = plot.data; + var bgn = SNR.CalculateModalBackgroundNoiseInSignal(scores, 1.0); + var mode = bgn.NoiseMode; + var sd = bgn.NoiseSd; + + // normalise the scores to z-scores + for (int i = 0; i < scores.Length; i++) + { + // Convert scores to z-scores + scores[i] = (scores[i] - mode) / sd; + if (scores[i] < 0.0) + { + scores[i] = 0.0; + } + + if (scores[i] > 4.0) + { + scores[i] = 4.0; + } + + // normalise full scale to 4 SDs. + scores[i] /= 4.0; + } + + opPlots.Add(plot); + } + + return opPlots; + } + public static List ConvertPlotDictionaryToPlotList(Dictionary dict) { var list = new List(); diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BaseContentType.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BaseContentType.cs index 4aa30a440..2f0307063 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BaseContentType.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BaseContentType.cs @@ -4,29 +4,87 @@ namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes { - using System; using System.Collections.Generic; + using System.IO; using System.Linq; - using System.Text; using TowseyLibrary; public abstract class BaseContentType { - public const int ReductionFactor = 16; + //TEMPLATE DESCRIPTION + // Name of the template + public const string Name = "UnknownContentType"; - public virtual string Name() => "Something"; + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "BaseNameOfFile"; - public static Dictionary GetTemplate(Dictionary dictionaryOfIndices) + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 0; + private const int EndRowId = 59; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. + private const int ReductionFactor = 16; + + // Bandpass filter to be applied + private const int FreqBinCount = 256 / ReductionFactor; + private const int BottomFreq = 0; //Hertz + private const int TopFreq = 11000; //Hertz + + // Only want the interval 3-4 kHz for Silver-eye band. + // After reducing 256 freq bins to 16, each bin has width 689Hz. + // Therefore to get band 3-4 kHz, need to remove the bottom and top bins. + // This leaves a template with 2 or 3 freq bins which are then averaged, so that each index has one value. + // At the present time this editing is done manually. + + private static readonly Dictionary SilverEyeTemplate = new Dictionary + { + ["ACI"] = new[] { 0.779 }, + ["ENT"] = new[] { 0.393 }, + ["EVN"] = new[] { 0.686 }, + ["BGN"] = new[] { 0.085 }, + ["PMN"] = new[] { 0.883 }, + }; + + public static KeyValuePair GetContent(Dictionary oneMinuteOfIndices) + { + var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor); + + //var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq); + //reducedIndices = ContentDescription.ApplyBandPass(reducedIndices, freqBinBounds[0], freqBinBounds[1]); + + var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices); + var templateVector = ContentDescription.ConvertDictionaryToVector(SilverEyeTemplate); + + //Get Euclidian distance and normalize the distance + // Now pass the template up the full frequency spectrum to get a spectrum of scores. + var spectralScores = ContentDescription.ScanSpectrumWithTemplate(SilverEyeTemplate, reducedIndices); + + // Now check how much of spectral weight is in the correct freq band ie between 3-4 kHz. + var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq, FreqBinCount); + double callSum = DataTools.Subarray(spectralScores, freqBinBounds[0], freqBinBounds[1]).Sum(); + double totalSum = DataTools.Subarray(spectralScores, 1, spectralScores.Length - 3).Sum(); + double score = callSum / totalSum; + + return new KeyValuePair(Name, score); + } + + public static Dictionary GetTemplate(DirectoryInfo dir) { - var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, 23, 27); - var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor); + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); + var birdIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); + var reducedIndices = ContentDescription.ReduceIndicesByFactor(birdIndices, ReductionFactor); + var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq, FreqBinCount); + reducedIndices = ContentDescription.ApplyBandPass(reducedIndices, freqBinBounds[0], freqBinBounds[1]); return reducedIndices; } - public static void WriteTemplateToFile(Dictionary dictionaryOfIndices, string path) + public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) { - var template = GetTemplate(dictionaryOfIndices); - FileTools.WriteDictionaryToFile(template, path); + var template = GetTemplate(ipDir); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); + FileTools.WriteDictionaryToFile(template, opPath); } // get dummy data diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BirdMorningChorus1.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BirdMorningChorus1.cs new file mode 100644 index 000000000..674a238d0 --- /dev/null +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/BirdMorningChorus1.cs @@ -0,0 +1,79 @@ +// +// 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). +// + +namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes +{ + using System; + using System.Collections.Generic; + using System.IO; + using TowseyLibrary; + + public class BirdMorningChorus1 + { + //TEMPLATE DESCRIPTION + // Name of the template + public const string Name = "BirdMorningChorus1"; + + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "SM304256_0+1_20151114_041652"; + + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 47; + private const int EndRowId = 53; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. + private const int ReductionFactor = 16; + + // Only want the interval 2-8 kHz for bird morning chorus. + // After reducing 256 freq bins to 16, each bin has width 689Hz. + // Therefore to get band 2-8 kHz, need to remove the bottom two freq bins and the top four freq bins. + // This leaves a template with 10 freq bins. + // At the present time this editing is done manually. + + private static readonly Dictionary BirdChorusTemplate = new Dictionary + { + ["ACI"] = new[] { 0.274, 0.366, 0.591, 0.820, 0.997, 0.975, 0.796, 0.846, 0.605, 0.260 }, + ["ENT"] = new[] { 0.293, 0.415, 0.804, 0.972, 0.910, 0.876, 0.923, 0.971, 0.840, 0.491 }, + ["EVN"] = new[] { 0.445, 0.691, 0.291, 0.266, 0.407, 0.417, 0.306, 0.321, 0.199, 0.091 }, + ["BGN"] = new[] { 0.140, 0.099, 0.072, 0.059, 0.055, 0.051, 0.048, 0.048, 0.045, 0.042 }, + ["PMN"] = new[] { 0.671, 0.967, 0.924, 0.998, 1.000, 1.000, 0.998, 1.000, 0.952, 0.633 }, + }; + + public static KeyValuePair GetContent(Dictionary oneMinuteOfIndices) + { + var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor); + + // remove first two freq bins and last four freq bins + int bottomBin = 2; + int topBin = 11; + reducedIndices = ContentDescription.ApplyBandPass(reducedIndices, bottomBin, topBin); + + var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices); + var templateVector = ContentDescription.ConvertDictionaryToVector(BirdChorusTemplate); + + //Get Euclidian distance and normalize the distance + var distance = DataTools.EuclidianDistance(templateVector, oneMinuteVector); + distance /= Math.Sqrt(templateVector.Length); + + return new KeyValuePair(Name, 1 - distance); + } + + public static Dictionary GetTemplate(DirectoryInfo dir) + { + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); + var birdIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); + var reducedIndices = ContentDescription.ReduceIndicesByFactor(birdIndices, ReductionFactor); + return reducedIndices; + } + + public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) + { + var template = GetTemplate(ipDir); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); + FileTools.WriteDictionaryToFile(template, opPath); + } + } +} diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainHeavy2.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainHeavy2.cs deleted file mode 100644 index 091c62257..000000000 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainHeavy2.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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). -// - -namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes -{ - using System; - using System.Collections.Generic; - using TowseyLibrary; - - public class RainHeavy2 - { - public const string Name = "HeavyRain2"; - private const int ReductionFactor = 16; - - private static Dictionary StrongRainTemplate = new Dictionary - { - ["ACI"] = new[] { 0.076, 0.046, 0.167, 0.360, 0.426, 0.443, 0.545, 0.595, 0.564, 0.612, 0.659, 0.570, 0.542, 0.520, 0.485, 0.485 }, - ["ENT"] = new[] { 0.065, 0.061, 0.176, 0.289, 0.249, 0.255, 0.296, 0.292, 0.262, 0.386, 0.462, 0.262, 0.222, 0.243, 0.217, 0.205 }, - ["EVN"] = new[] { 0.136, 0.009, 0.022, 0.051, 0.072, 0.092, 0.109, 0.150, 0.175, 0.176, 0.193, 0.155, 0.171, 0.135, 0.109, 0.133 }, - ["BGN"] = new[] { 0.366, 0.249, 0.181, 0.148, 0.122, 0.111, 0.106, 0.105, 0.104, 0.111, 0.111, 0.111, 0.105, 0.100, 0.090, 0.048 }, - ["PMN"] = new[] { 0.182, 0.076, 0.243, 0.459, 0.470, 0.501, 0.592, 0.651, 0.625, 0.699, 0.792, 0.599, 0.572, 0.550, 0.490, 0.488 }, - }; - - public static KeyValuePair GetStrongRainContent(Dictionary oneMinuteOfIndices) - { - var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor); - var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices); - var templateVector = ContentDescription.ConvertDictionaryToVector(StrongRainTemplate); - - //Get Euclidian distance and normalise the distance - var distance = DataTools.EuclidianDistance(templateVector, oneMinuteVector); - distance /= Math.Sqrt(templateVector.Length); - - // get dummy data - //var rn = new RandomNumber(DateTime.Now.Second + (int)DateTime.Now.Ticks + 333); - //var distance = rn.GetDouble(); - - return new KeyValuePair(Name, 1 - distance); - } - - //THESE ARE SPECIFIC BOUNDS FOR PREPARING THIS TEMPLATE - private const int StartRowId = 32; - private const int EndRowId = 36; - - /// - /// string baseName = "SM304256_0+1_20151114_071652". - /// - public static Dictionary GetTemplate(Dictionary dictionaryOfIndices) - { - var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); - var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor); - return reducedIndices; - } - - public static void WriteTemplateToFile(Dictionary dictionaryOfIndices, string path) - { - var template = GetTemplate(dictionaryOfIndices); - FileTools.WriteDictionaryToFile(template, path); - } - } -} diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainLight1.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainLight1.cs index 82c5138c3..e6cecb3f3 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainLight1.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/RainLight1.cs @@ -6,14 +6,28 @@ namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes { using System; using System.Collections.Generic; + using System.IO; using TowseyLibrary; public class RainLight1 { + //TEMPLATE DESCRIPTION + // Name of the template public const string Name = "LightRain1"; + + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "SM304256_0+1_20151114_071652"; + + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 32; + private const int EndRowId = 36; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. private const int ReductionFactor = 16; - private static Dictionary LightRainTemplate = new Dictionary + private static readonly Dictionary LightRainTemplate = new Dictionary { ["ACI"] = new[] { 0.076, 0.046, 0.167, 0.360, 0.426, 0.443, 0.545, 0.595, 0.564, 0.612, 0.659, 0.570, 0.542, 0.520, 0.485, 0.485 }, ["ENT"] = new[] { 0.065, 0.061, 0.176, 0.289, 0.249, 0.255, 0.296, 0.292, 0.262, 0.386, 0.462, 0.262, 0.222, 0.243, 0.217, 0.205 }, @@ -39,24 +53,22 @@ public static KeyValuePair GetContent(Dictionary(Name, 1 - distance); } - //THESE ARE SPECIFIC BOUNDS FOR PREPARING THIS TEMPLATE - private const int StartRowId = 32; - private const int EndRowId = 36; - /// /// string baseName = "SM304256_0+1_20151114_071652". /// - public static Dictionary GetRainTemplate(Dictionary dictionaryOfIndices) + public static Dictionary GetTemplate(DirectoryInfo dir) { + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor); return reducedIndices; } - public static void WriteLightRainTemplateToFile(Dictionary dictionaryOfIndices, string path) + public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) { - var template = GetRainTemplate(dictionaryOfIndices); - FileTools.WriteDictionaryToFile(template, path); + var template = GetTemplate(ipDir); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); + FileTools.WriteDictionaryToFile(template, opPath); } } } diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/SilverEyeMezTasmanIs.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/SilverEyeMezTasmanIs.cs new file mode 100644 index 000000000..1083209ed --- /dev/null +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/SilverEyeMezTasmanIs.cs @@ -0,0 +1,91 @@ +// +// 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). +// + +namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using TowseyLibrary; + + public class SilverEyeMezTasmanIs + { + //TEMPLATE DESCRIPTION + // Name of the template + public const string Name = "SilverEyeMezTasmanIs"; + + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "SM304256_0+1_20151114_071652"; + + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 6; + private const int EndRowId = 11; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. + private const int ReductionFactor = 16; + + // Bandpass filter to be applied + private const int FreqBinCount = 256 / ReductionFactor; + private const int BottomFreq = 3000; //Hertz + private const int TopFreq = 4000; //Hertz + + // Only want the interval 3-4 kHz for Silver-eye band. + // After reducing 256 freq bins to 16, each bin has width 689Hz. + // Therefore to get band 3-4 kHz, need to remove the bottom and top bins. + // This leaves a template with 2 or 3 freq bins which are then averaged, so that each index has one value. + // At the present time this editing is done manually. + + private static readonly Dictionary SilverEyeTemplate = new Dictionary + { + ["ACI"] = new[] { 0.779 }, + ["ENT"] = new[] { 0.393 }, + ["EVN"] = new[] { 0.686 }, + ["BGN"] = new[] { 0.085 }, + ["PMN"] = new[] { 0.883 }, + }; + + public static KeyValuePair GetContent(Dictionary oneMinuteOfIndices) + { + var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor); + + //var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq); + //reducedIndices = ContentDescription.ApplyBandPass(reducedIndices, freqBinBounds[0], freqBinBounds[1]); + + var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices); + var templateVector = ContentDescription.ConvertDictionaryToVector(SilverEyeTemplate); + + //Get Euclidian distance and normalize the distance + // Now pass the template up the full frequency spectrum to get a spectrum of scores. + var spectralScores = ContentDescription.ScanSpectrumWithTemplate(SilverEyeTemplate, reducedIndices); + + // Now check how much of spectral weight is in the correct freq band ie between 3-4 kHz. + var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq, FreqBinCount); + double callSum = DataTools.Subarray(spectralScores, freqBinBounds[0], freqBinBounds[1]).Sum(); + double totalSum = DataTools.Subarray(spectralScores, 1, spectralScores.Length - 3).Sum(); + double score = callSum / totalSum; + + return new KeyValuePair(Name, score); + } + + public static Dictionary GetTemplate(DirectoryInfo dir) + { + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); + var birdIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); + var reducedIndices = ContentDescription.ReduceIndicesByFactor(birdIndices, ReductionFactor); + var freqBinBounds = ContentDescription.GetFreqBinBounds(BottomFreq, TopFreq, FreqBinCount); + reducedIndices = ContentDescription.ApplyBandPass(reducedIndices, freqBinBounds[0], freqBinBounds[1]); + return reducedIndices; + } + + public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) + { + var template = GetTemplate(ipDir); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); + FileTools.WriteDictionaryToFile(template, opPath); + } + } +} diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindLight1.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindLight1.cs index 53a25cc55..53a7a7f52 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindLight1.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindLight1.cs @@ -11,10 +11,23 @@ namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes public class WindLight1 { + //TEMPLATE DESCRIPTION + // Name of the template public const string Name = "LightWind1"; + + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "SM304256_0+1_20151114_021652"; + + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 50; + private const int EndRowId = 53; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. private const int ReductionFactor = 16; - private static Dictionary LightWindTemplate = new Dictionary + private static readonly Dictionary LightWindTemplate = new Dictionary { ["ACI"] = new[] { 0.072, 0.035, 0.015, 0.008, 0.008, 0.009, 0.016, 0.018, 0.017, 0.015, 0.020, 0.022, 0.029, 0.026, 0.027, 0.098 }, ["ENT"] = new[] { 0.063, 0.069, 0.071, 0.059, 0.052, 0.051, 0.050, 0.050, 0.050, 0.050, 0.050, 0.052, 0.051, 0.050, 0.051, 0.058 }, @@ -26,6 +39,9 @@ public class WindLight1 public static KeyValuePair GetContent(Dictionary oneMinuteOfIndices) { var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor); + reducedIndices.Remove("ENT"); + LightWindTemplate.Remove("ENT"); + var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices); var templateVector = ContentDescription.ConvertDictionaryToVector(LightWindTemplate); @@ -38,12 +54,8 @@ public static KeyValuePair GetContent(Dictionary GetTemplate(DirectoryInfo dir) { - //THESE ARE SPECIFIC BOUNDS FOR PREPARING THIS TEMPLATE - int startRowId = 50; - int endRowId = 53; - string baseName = "SM304256_0+1_20151114_021652"; - var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, baseName); - var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, startRowId, endRowId); + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); + var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor); return reducedIndices; } @@ -51,7 +63,7 @@ public static Dictionary GetTemplate(DirectoryInfo dir) public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) { var template = GetTemplate(ipDir); - var opPath = Path.Combine(opDir.FullName, "LightWindTemplate1.csv"); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); FileTools.WriteDictionaryToFile(template, opPath); } } diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindStrong1.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindStrong1.cs index 92cb725e6..9c191f366 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindStrong1.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentTypes/WindStrong1.cs @@ -6,14 +6,28 @@ namespace AudioAnalysisTools.ContentDescriptionTools.ContentTypes { using System; using System.Collections.Generic; + using System.IO; using TowseyLibrary; public class WindStrong1 { + //TEMPLATE DESCRIPTION + // Name of the template public const string Name = "StrongWind1"; + + // The TEMPLATE PROVENANCE + // The source file name from which the indices are extracted. + private const string BaseName = "SM304256_0+1_20151114_011652"; + + //THESE ARE SPECIFIC ROW BOUNDS FOR PREPARING THIS TEMPLATE + // The freq bins will be averaged over the time period. + private const int StartRowId = 23; + private const int EndRowId = 27; + + // Full array (256 freq bins) of spectral indices is reduced by the following factor by averaging. private const int ReductionFactor = 16; - private static Dictionary StrongWindTemplate = new Dictionary + private static readonly Dictionary StrongWindTemplate = new Dictionary { ["ACI"] = new[] { 0.086, 0.043, 0.041, 0.023, 0.032, 0.027, 0.029, 0.031, 0.032, 0.032, 0.034, 0.069, 0.033, 0.024, 0.018, 0.018 }, ["ENT"] = new[] { 0.124, 0.112, 0.146, 0.163, 0.157, 0.157, 0.143, 0.122, 0.113, 0.095, 0.087, 0.121, 0.075, 0.060, 0.054, 0.067 }, @@ -35,21 +49,19 @@ public static KeyValuePair GetContent(Dictionary(Name, 1 - distance); } - //THESE ARE SPECIFIC BOUNDS FOR PREPARING THIS TEMPLATE - private const int StartRowId = 23; - private const int EndRowId = 27; - - public static Dictionary GetTemplate(Dictionary dictionaryOfIndices) + public static Dictionary GetTemplate(DirectoryInfo dir) { + var dictionaryOfIndices = ContentDescription.ReadIndexMatrices(dir, BaseName); var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, StartRowId, EndRowId); var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor); return reducedIndices; } - public static void WriteTemplateToFile(Dictionary dictionaryOfIndices, string path) + public static void WriteTemplateToFile(DirectoryInfo ipDir, DirectoryInfo opDir) { - var template = GetTemplate(dictionaryOfIndices); - FileTools.WriteDictionaryToFile(template, path); + var template = GetTemplate(ipDir); + var opPath = Path.Combine(opDir.FullName, Name + "Template.csv"); + FileTools.WriteDictionaryToFile(template, opPath); } } }