From 9c13b79e2fb982b03bda7a805bab3a683a103d15 Mon Sep 17 00:00:00 2001 From: towsey Date: Tue, 22 Oct 2019 20:32:54 +1000 Subject: [PATCH] Finally get content description working in IAnalyzer2 environment. Issue #252 Finally get content description working in IAnalyzer2 environment. --- src/AnalysisPrograms/ContentDescription.cs | 52 +++++++++++++------ src/AnalysisPrograms/Sandpit.cs | 6 +-- .../ContentSignatures.cs | 24 ++++++--- .../ContentDescriptionTools/DataProcessing.cs | 41 ++++++++------- src/TowseyLibrary/FileTools.cs | 32 ++++++++++++ src/TowseyLibrary/Plot.cs | 16 ++++-- 6 files changed, 125 insertions(+), 46 deletions(-) diff --git a/src/AnalysisPrograms/ContentDescription.cs b/src/AnalysisPrograms/ContentDescription.cs index fe77a4e20..9902910ed 100644 --- a/src/AnalysisPrograms/ContentDescription.cs +++ b/src/AnalysisPrograms/ContentDescription.cs @@ -10,13 +10,11 @@ namespace AnalysisPrograms using System.IO; using System.Linq; using System.Reflection; - using Accord; using Acoustics.Shared; using Acoustics.Shared.ConfigFile; using Acoustics.Shared.Csv; using AnalysisBase; using AnalysisBase.ResultBases; - using AudioAnalysisTools; using AudioAnalysisTools.ContentDescriptionTools; using AudioAnalysisTools.Indices; using AudioAnalysisTools.LongDurationSpectrograms; @@ -201,13 +199,18 @@ public override void SummariseResults( // now get the content description for each minute. //TODO TODO TODO TODO GET FILE NAME FROM CONFIG.YML FILE - var path1 = Path.Combine(configDirectory, ContentSignatures.TemplatesFileName); - var templatesFile = new FileInfo(path1); - var contentPlots = GetContentDescription(spectralIndices, templatesFile, startTimeOffset); + var templatesFile = new FileInfo(Path.Combine(configDirectory, ContentSignatures.TemplatesFileName)); + var contentDictionary = GetContentDescription(spectralIndices, templatesFile, startTimeOffset); + + // Write the results to a csv file + var filePath = Path.Combine(resultsDirectory.FullName, "AcousticSignatures.csv"); + FileTools.WriteDictionaryAsCsvFile(contentDictionary, filePath); + + // prepare graphical plots of the acoustic signatures. + var contentPlots = GetPlots(contentDictionary); var images = GraphsAndCharts.DrawPlotDistributions(contentPlots); var plotsImage = ImageTools.CombineImagesVertically(images); - var path2 = Path.Combine(resultsDirectory.FullName, "DistributionsOfContentScores.png"); - plotsImage.Save(path2); + plotsImage.Save(Path.Combine(resultsDirectory.FullName, "DistributionsOfContentScores.png")); // Attach content description plots to LDFC spectrogram and write to file var ldfcSpectrogram = Image.FromFile(ldfcSpectrogramPath); @@ -222,7 +225,10 @@ public override void SummariseResults( /// set of spectral indices for each minute. /// json file containing description of templates. /// minute Id for start of recording. - public static List GetContentDescription(SpectralIndexBase[] spectralIndices, FileInfo templatesFile, TimeSpan elapsedTimeAtStartOfRecording) + public static Dictionary GetContentDescription( + SpectralIndexBase[] spectralIndices, + FileInfo templatesFile, + TimeSpan elapsedTimeAtStartOfRecording) { // Read in the content description templates var templates = Json.Deserialize(templatesFile); @@ -242,6 +248,15 @@ public static List GetContentDescription(SpectralIndexBase[] spectralIndic // Transfer acoustic indices to dictionary var indicesDictionary = IndexCalculateSixOnly.ConvertIndicesToDictionary(oneMinuteOfIndices); + // normalize the index values + foreach (string key in ContentSignatures.IndexNames) + { + var indexBounds = ContentSignatures.IndexValueBounds[key]; + var indexArray = indicesDictionary[key]; + var normalisedVector = DataTools.NormaliseInZeroOne(indexArray, indexBounds[0], indexBounds[1]); + indicesDictionary[key] = normalisedVector; + } + // scan templates over one minute of indices var resultsForOneMinute = ContentSignatures.AnalyzeOneMinute( templates, @@ -251,14 +266,21 @@ public static List GetContentDescription(SpectralIndexBase[] spectralIndic results.Add(resultsForOneMinute); } - // Write the results to file - // TODO TODO - var dictionaryOfScores = DataProcessing.ConvertResultsToArrays(results, length, startMinuteId); - var fileName = "zzz"; - //DataProcessing.WriteScoresToFile(fileName); - + // convert results to dictionary of score arrays // TODO TODO fix the below plotLength and start. - var plotDict = DataProcessing.ConvertResultsToPlots(results, 1440, 0); + var dictionaryOfScores = DataProcessing.ConvertResultsToDictionaryOfArrays(results, length, startMinuteId); + return dictionaryOfScores; + } + + /// + /// Produce plots for graphical display. + /// NOTE: The threshold can be changed later. + /// + /// A list of graphical plots. + private static List GetPlots(Dictionary contentDictionary) + { + double threshold = 0.25; + var plotDict = DataProcessing.ConvertArraysToPlots(contentDictionary, threshold); var contentPlots = DataProcessing.ConvertPlotDictionaryToPlotList(plotDict); // convert scores to z-scores diff --git a/src/AnalysisPrograms/Sandpit.cs b/src/AnalysisPrograms/Sandpit.cs index c275df35e..b4b549661 100644 --- a/src/AnalysisPrograms/Sandpit.cs +++ b/src/AnalysisPrograms/Sandpit.cs @@ -368,10 +368,10 @@ public static void Audio2CsvOverOneFile() //string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.Acoustic.yml"; // Test on Content Description - string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding.wav"; - //string recordingPath = @"C:\Ecoacoustics\WavFiles\LizZnidersic\TasmanIsland2015_Unit2_Mez\SM304256_0+1_20151114_131652+1000.wav"; + //string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding.wav"; + string recordingPath = @"C:\Ecoacoustics\WavFiles\LizZnidersic\TasmanIsland2015_Unit2_Mez\SM304256_0+1_20151114_131652+1000.wav"; //string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\14"; - string outputPath = @"C:\Ecoacoustics\ContentDescription\TestOfSixIndices"; + string outputPath = @"C:\Ecoacoustics\ContentDescription\Test6IndicesMez13"; string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.ContentDescription.yml"; // Ivan Campos recordings diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs index ffa864c42..2ec6d7fac 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs @@ -21,7 +21,7 @@ public class ContentSignatures public const string AnalysisString = "__Towsey.Acoustic."; - //TODO TODO TODO TODO GET FILE NAME FROM CONFIG.YML FILE + //TODO TODO GET FILE NAME FROM CONFIG.YML FILE public const string TemplatesFileName = "Towsey.TemplateDefinitions.json"; /// @@ -50,8 +50,11 @@ public class ContentSignatures /// A list of plots - each plot is the minute by minute scores for a single template. public static List ContentDescriptionOfMultipleRecordingFiles(FileInfo listOfIndexFiles, FileInfo templatesFile) { - // Read in all template manifests - //var templates = Yaml.Deserialize(templatesFile); + // total length in minutes of all the recordings + const int totalMinutesDurationOverAllRecordings = 1440; + const int startMinute = 0; + + // Read in all the prepared templates var templates = Json.Deserialize(templatesFile); var templatesAsDictionary = DataProcessing.ExtractDictionaryOfTemplateDictionaries(templates); @@ -67,7 +70,8 @@ public static List ContentDescriptionOfMultipleRecordingFiles(FileInfo lis // cycle through the directories for (int i = 0; i < filePaths.Count; i++) { - // read the spectral indices for the current file + // read the spectral indices for the current file. + //IMPORTANT: This method returns normalised index values var dictionaryOfRecordingIndices = DataProcessing.ReadIndexMatrices(filePaths[i]); // Draw the index matrices for check/debug purposes @@ -79,11 +83,11 @@ public static List ContentDescriptionOfMultipleRecordingFiles(FileInfo lis completeListOfResults.AddRange(results); // calculate the elapsed minutes in this recording - var matrix = dictionaryOfRecordingIndices["ENT"]; + var matrix = dictionaryOfRecordingIndices.FirstValue(); elapsedMinutes += matrix.GetLength(0); } - var plotDict = DataProcessing.ConvertResultsToPlots(completeListOfResults, 1440, 0); + var plotDict = DataProcessing.ConvertResultsToPlots(completeListOfResults, totalMinutesDurationOverAllRecordings, startMinute); var contentPlots = DataProcessing.ConvertPlotDictionaryToPlotList(plotDict); // convert scores to z-scores @@ -124,6 +128,14 @@ public static List AnalyzeMinutes( return results; } + /// + /// IMPORTANT: The indices passed in the dictionary "oneMinuteOfIndices" must be normalised. + /// + /// The templates read from json file. + /// The numerical pasrt of each template. + /// The normalised values of the indices derived from one minute of recording. + /// The minute ID, i.e. its temporal position. + /// A single instance of a DescriptionResult. public static DescriptionResult AnalyzeOneMinute( TemplateManifest[] templates, Dictionary> templatesAsDictionary, diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs b/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs index 887b143a5..9269ffdc6 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs @@ -32,6 +32,7 @@ public static Dictionary ConvertTemplateArrayToDiction /// /// Reads in all the index matrices whose keys are in the above array of IndexNames. + /// Returns normalised values. /// /// Partial path to the index files. /// a Dictionary of matrices containing normalised index values. @@ -346,13 +347,13 @@ public static double[] GetFreqBinVector(Dictionary dictionary, /// Converts individual results to a dictionary of plots. /// /// a list of results for each content type in every minute. - /// The plot length will the total number of minutes scanned, typically 1440 or one day. + /// The plot length will the total number of minutes scanned, typically 1440 for one day. /// time start. - public static Dictionary ConvertResultsToArrays(List results, int arrayLength, int arrayStart) + public static Dictionary ConvertResultsToDictionaryOfArrays(List results, int arrayLength, int arrayStart) { var arrays = new Dictionary(); - foreach (DescriptionResult result in results) + foreach (var result in results) { var time = (int)Math.Round(result.StartTimeInCurrentRecordingFile.TotalMinutes); var dict = result.GetDescriptionDictionary(); @@ -383,26 +384,30 @@ public static Dictionary ConvertResultsToArrays(Listtime start. public static Dictionary ConvertResultsToPlots(List results, int plotLength, int plotStart) { + var dictOfArrays = ConvertResultsToDictionaryOfArrays(results, plotLength, plotStart); var plots = new Dictionary(); - foreach (DescriptionResult result in results) + foreach (var kvp in dictOfArrays) { - var time = (int)Math.Round(result.StartTimeInCurrentRecordingFile.TotalMinutes); - var dict = result.GetDescriptionDictionary(); - foreach (KeyValuePair kvp in dict) - { - var name = kvp.Key; - var value = kvp.Value; + var name = kvp.Key; + var value = kvp.Value; + var plot = new Plot(name, value, 0.25); // NOTE: The threshold can be changed later. + plots.Add(name, plot); + } - if (!plots.ContainsKey(name)) - { - var scores = new double[plotLength]; - var plot = new Plot(name, scores, 0.25); // NOTE: The threshold can be changed later. - plots.Add(name, plot); - } + return plots; + } - plots[name].data[plotStart + time] = value; - } + public static Dictionary ConvertArraysToPlots(Dictionary dictOfArrays, double threshold) + { + var plots = new Dictionary(); + + foreach (var kvp in dictOfArrays) + { + var name = kvp.Key; + var value = kvp.Value; + var plot = new Plot(name, value, threshold); + plots.Add(name, plot); } return plots; diff --git a/src/TowseyLibrary/FileTools.cs b/src/TowseyLibrary/FileTools.cs index 7b78b3fe4..730aed6df 100644 --- a/src/TowseyLibrary/FileTools.cs +++ b/src/TowseyLibrary/FileTools.cs @@ -335,6 +335,38 @@ public static void WriteDictionaryToFile(Dictionary dictionary WriteTextFile(fPath, text.ToString()); } + /// + /// Write a dictionary of arrays as a csv file where dictionary keys are column headers and the + /// arrays are the column entries. + /// WARNING: Assume that all arrays are of the same size!. + /// + /// a dictionary of arrays of double. + /// The file path. + public static void WriteDictionaryAsCsvFile(Dictionary dictionary, string fPath) + { + var kvp1 = dictionary.First(); + var arrayLength = kvp1.Value.Length; + + // set up an array of lines with first line for headers + var lines = new string[arrayLength + 1]; + + // now build up the lines + foreach (KeyValuePair kvp in dictionary) + { + // add in the header + lines[0] += kvp.Key + ","; + + // now add in values + var array = kvp.Value; + for (int i = 0; i < array.Length; i++) + { + lines[i + 1] += $"{array[i]:F3}, "; + } + } + + WriteTextFile(fPath, lines); + } + public static void Append2TextFile(string fPath, string line) { bool saveExistingFile = false; diff --git a/src/TowseyLibrary/Plot.cs b/src/TowseyLibrary/Plot.cs index 5efd3a46b..d408a6e02 100644 --- a/src/TowseyLibrary/Plot.cs +++ b/src/TowseyLibrary/Plot.cs @@ -137,11 +137,19 @@ public Image DrawAnnotatedPlot(int height) // var font = new Font(family, 10, FontStyle.Regular, GraphicsUnit.Pixel); var font = new Font("Tahoma", 9); var g = Graphics.FromImage(image); - g.DrawString(this.title, font, Brushes.Red, new PointF(10, 0)); + g.DrawString(this.title, font, Brushes.Red, new PointF(8, 0)); + + if (this.data.Length > 500) + { + g.DrawString(this.title, font, Brushes.Red, new PointF(length - 80, 0)); + } + + if (this.data.Length > 1200) + { + // ReSharper disable once PossibleLossOfFraction + g.DrawString(this.title, font, Brushes.Red, new PointF(length / 2, 0)); + } - // ReSharper disable once PossibleLossOfFraction - g.DrawString(this.title, font, Brushes.Red, new PointF(length / 2, 0)); - g.DrawString(this.title, font, Brushes.Red, new PointF(length - 80, 0)); return image; }