Skip to content

Commit

Permalink
Building more of code structure for content descirption
Browse files Browse the repository at this point in the history
Issue #252 Writing methods to calculate and display acoustic content.
  • Loading branch information
towsey committed Sep 7, 2019
1 parent 4847c10 commit d2511f4
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 22 deletions.
34 changes: 20 additions & 14 deletions src/AnalysisPrograms/Sandpit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,27 +120,33 @@ public override Task<int> Execute(CommandLineApplication app)

public static void ContentDescriptionDev()
{
var dir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez02\Towsey.Acoustic");
string baseName = "SM304256_0+1_20151114_011652";
var dictionary = ContentDescription.ReadIndexMatrices(dir, baseName);
//var dir = new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez02\Towsey.Acoustic");

// Draw the index matrices for check/debug purposes
// var dir1 = new DirectoryInfo(@"C:\Ecoacoustics\Output\ContentDescription");
// ContentDescription.DrawNormalisedIndexMatrices(dir1, baseName, dictionary);
DirectoryInfo[] directories =
{
new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez01\Towsey.Acoustic"),
new DirectoryInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\Mez02\Towsey.Acoustic"),
};

string[] baseNames = { "SM304256_0+1_20151114_001652", "SM304256_0+1_20151114_011652" };

//PREPARE STRONG WIND TEMPLATE
//var path2 = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "StrongWindTemplate.csv");
//WindContent.WriteStrongWindTemplateToFile(dictionary, path2);

// get the rows and do something with them one by one.
var scores = ContentDescription.AnalyseMinutes(dictionary);
var contentPlots = ContentDescription.ContentDescriptionOfMultipleRecordingFiles(directories, baseNames);

// Attach content description plots to LDFC spectrogram
var path = Path.Combine(@"C:\Ecoacoustics\Output\Test\Test24HourRecording", "Testing__2Maps.png");
var ldfcSpectrogram = Image.FromFile(path);
var contentScores = new List<Plot>
{
ContentDescription.GetRandomNumberArray(ldfcSpectrogram.Width),
};

var image = ContentDescription.DrawLdfcSpectrogramWithContentScoreTracks(ldfcSpectrogram, contentScores);
var path1 = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "Testing__2Maps.CONTENT.png");
//var contentScores = new List<Plot>
//{
// ContentDescription.GetRandomNumberArray(ldfcSpectrogram.Width),
//};

var image = ContentDescription.DrawLdfcSpectrogramWithContentScoreTracks(ldfcSpectrogram, contentPlots);
var path1 = Path.Combine(@"C:\Ecoacoustics\Output\ContentDescription", "Testing__2Maps.CONTENT2.png");
image.Save(path1);
}

Expand Down
2 changes: 2 additions & 0 deletions src/AudioAnalysisTools/AudioAnalysisTools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@
<Compile Include="BirdClefExperiment1.cs" />
<Compile Include="ChannelIntegrity.cs" />
<Compile Include="ContentDescriptionTools\ContentDescription.cs" />
<Compile Include="ContentDescriptionTools\DescriptionResult.cs" />
<Compile Include="ContentDescriptionTools\RainContent.cs" />
<Compile Include="ContentDescriptionTools\WindContent.cs" />
<Compile Include="CrossCorrelation.cs" />
<Compile Include="DSP\Clipping.cs" />
Expand Down
138 changes: 133 additions & 5 deletions src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ public class ContentDescription

public static string[] IndexNames { get; } = { "ACI", "ENT", "EVN", "BGN", "PMN" };

public static List<Plot> ContentDescriptionOfMultipleRecordingFiles(DirectoryInfo[] directories, string[] baseNames)
{
// init a list to collect description results
var completeListOfResults = new List<DescriptionResult>();

// cycle through the directories
for (int i = 0; i < directories.Length; i++)
{
// read the spectral indices for the current file
var dictionary = ContentDescription.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
completeListOfResults.AddRange(results);
}

var plotDict = ContentDescription.ConvertResultsToPlots(completeListOfResults, 1440, 0);
var contentPlots = ContentDescription.ConvertPlotDictionaryToPlotList(plotDict);
return contentPlots;
}

/// <summary>
/// Reads in all the index matrices whose keys are in the above array of IndexNames.
/// </summary>
Expand Down Expand Up @@ -132,25 +157,32 @@ public static Dictionary<string, double[,]> ReadIndexMatrices(DirectoryInfo dir,
return opMatrix;
}

public static double[] AnalyseMinutes(Dictionary<string, double[,]> dictionary)
public static List<DescriptionResult> AnalyseMinutes(Dictionary<string, double[,]> dictionary, int elapsedMinutes)
{
int rowCount = dictionary[ContentDescription.IndexNames[0]].GetLength(0);
int freqBinCount = dictionary[ContentDescription.IndexNames[0]].GetLength(1);
var results = new List<DescriptionResult>();

// over all rows assuming one minute per row.
for (int i = 0; i < rowCount; i++)
{
var oneMinuteOfIndices = GetIndicesForOneMinute(dictionary, i);
var descriptionResult = new DescriptionResult(elapsedMinutes + i);

// now send indices to various content searches
var windScores = WindContent.GetStrongWindContent(oneMinuteOfIndices);
descriptionResult.AddDescription(WindContent.GetStrongWindContent(oneMinuteOfIndices));
descriptionResult.AddDescription(WindContent.GetLightWindContent(oneMinuteOfIndices));
descriptionResult.AddDescription(RainContent.GetStrongRainContent(oneMinuteOfIndices));
descriptionResult.AddDescription(RainContent.GetLightRainContent(oneMinuteOfIndices));

results.Add(descriptionResult);
}

var scores = new double[rowCount];
return scores;
return results;
}

public static Dictionary<string, double[]> GetIndicesForOneMinute(Dictionary<string, double[,]> allIndices, int rowId)
public static Dictionary<string, double[]> GetIndicesForOneMinute(Dictionary<string, double[,]> allIndices,
int rowId)
{
var opIndices = new Dictionary<string, double[]>();

Expand All @@ -167,6 +199,64 @@ public static Dictionary<string, double[]> GetIndicesForOneMinute(Dictionary<str
return opIndices;
}

public static Dictionary<string, double[]> AverageIndicesOverMinutes(Dictionary<string, double[,]> allIndices, int startRowId, int endRowId)
{
var opIndices = new Dictionary<string, double[]>();

var keys = allIndices.Keys;
foreach (string key in keys)
{
var success = allIndices.TryGetValue(key, out double[,] matrix);
if (success)
{
var colCount = matrix.GetLength(1);
var subMatrix = MatrixTools.Submatrix(matrix, startRowId, 0, endRowId, colCount - 1);
opIndices.Add(key, MatrixTools.GetColumnAverages(subMatrix));
}
}

return opIndices;
}

/// <summary>
/// Reduces a dictionary of vectors by a factor. It is assumed that the input vectors are a power of 2 in length i.e. FFT spectra.
/// It is assumed that the factor of reduction will also be a power of 2, typically 8 or 16.
/// </summary>
/// <returns>The dictionary of reduced vectors.</returns>
public static Dictionary<string, double[]> ReduceIndicesByFactor(Dictionary<string, double[]> indices, int factor)
{
var opIndices = new Dictionary<string, double[]>();

var keys = indices.Keys;
foreach (string key in keys)
{
var success = indices.TryGetValue(key, out double[] vector);
if (success)
{
var opVector = DataTools.VectorReduceLength(vector, factor);
opIndices.Add(key, opVector);
}
}

return opIndices;
}

public static double[] ConvertDictionaryToVector(Dictionary<string, double[]> dictionary)
{
var list = new List<double>();
var keys = dictionary.Keys;
foreach (string key in keys)
{
var success = dictionary.TryGetValue(key, out double[] indices);
if (success)
{
list.AddRange(indices);
}
}

return list.ToArray();
}

public static void DrawNormalisedIndexMatrices(DirectoryInfo dir, string baseName, Dictionary<string, double[,]> dictionary)
{
var list = new List<Image>();
Expand Down Expand Up @@ -207,6 +297,44 @@ public static void DrawNormalisedIndexMatrices(DirectoryInfo dir, string baseNam
indexImage?.Save(path);
}

public static Dictionary<string, Plot> ConvertResultsToPlots(List<DescriptionResult> results, int plotLength, int plotStart)
{
var plots = new Dictionary<string, Plot>();

foreach (DescriptionResult result in results)
{
var time = (int)Math.Round(result.StartTimeInCurrentRecordingFile.TotalMinutes);
var dict = result.GetDescriptionDictionary();
foreach (KeyValuePair<string, double> kvp in dict)
{
var name = kvp.Key;
var value = kvp.Value;

if (!plots.ContainsKey(name))
{
var scores = new double[plotLength];
var plot = new Plot(name, scores, 0.25);
plots.Add(name, plot);
}

plots[name].data[plotStart + time] = value;
}
}

return plots;
}

public static List<Plot> ConvertPlotDictionaryToPlotList(Dictionary<string, Plot> dict)
{
var list = new List<Plot>();
foreach (KeyValuePair<string, Plot> kvp in dict)
{
list.Add(kvp.Value);
}

return list;
}

public static Image DrawLdfcSpectrogramWithContentScoreTracks(Image ldfcSpectrogram, List<Plot> contentScores)
{
int trackHeight = 30;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AudioAnalysisTools.ContentDescriptionTools
{
/// <summary>
/// This class holds the results of content description for a unit of recording, assumed to be one-minute.
/// The results are held in a dictionary.
/// </summary>
public class DescriptionResult
{
private readonly Dictionary<string, double> descriptionDictionary = new Dictionary<string, double>();

public DescriptionResult(int startTimeInMinutes)
{
this.StartTimeInCurrentRecordingFile = TimeSpan.FromMinutes(startTimeInMinutes);
}

public TimeSpan StartTimeInCurrentRecordingFile { get; set; }

public void AddDescription(KeyValuePair<string, double> kvp) => this.descriptionDictionary.Add(kvp.Key, kvp.Value);

public Dictionary<string, double> GetDescriptionDictionary() => this.descriptionDictionary;
}
}
29 changes: 29 additions & 0 deletions src/AudioAnalysisTools/ContentDescriptionTools/RainContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AudioAnalysisTools.ContentDescriptionTools
{
using TowseyLibrary;

public static class RainContent
{
public static KeyValuePair<string, double> GetStrongRainContent(Dictionary<string, double[]> oneMinuteOfIndices)
{
const string name = "StrongRain1";
var rn = new RandomNumber((int)DateTime.Now.Ticks + 27);
var score = rn.GetDouble();
return new KeyValuePair<string, double>(name, score);
}

public static KeyValuePair<string, double> GetLightRainContent(Dictionary<string, double[]> oneMinuteOfIndices)
{
const string name = "LightRain1";
var rn = new RandomNumber(DateTime.Now.Millisecond + 9);
var score = rn.GetDouble();
return new KeyValuePair<string, double>(name, score);
}
}
}
53 changes: 50 additions & 3 deletions src/AudioAnalysisTools/ContentDescriptionTools/WindContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,63 @@

namespace AudioAnalysisTools.ContentDescriptionTools
{
using System.IO;
using TowseyLibrary;

public static class WindContent
{
private const int ReductionFactor = 16;

public static double GetStrongWindContent(Dictionary<string, double[]> oneMinuteOfIndices)
private static Dictionary<string, double[]> StrongWindTemplate = new Dictionary<string, double[]>
{
var rn = new RandomNumber();
["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 },
["EVN"] = new[] { 0.376, 0.440, 0.590, 0.621, 0.648, 0.621, 0.565, 0.363, 0.273, 0.191, 0.164, 0.221, 0.104, 0.040, 0.017, 0.032 },
["BGN"] = new[] { 0.472, 0.360, 0.273, 0.199, 0.156, 0.121, 0.096, 0.085, 0.075, 0.069, 0.064, 0.061, 0.060, 0.058, 0.054, 0.026 },
["PMN"] = new[] { 0.468, 0.507, 0.687, 0.743, 0.757, 0.751, 0.665, 0.478, 0.391, 0.317, 0.276, 0.367, 0.187, 0.109, 0.071, 0.096 },
};

public static KeyValuePair<string, double> GetStrongWindContent(Dictionary<string, double[]> oneMinuteOfIndices)
{
const string name = "StrongWind1";

var reducedIndices = ContentDescription.ReduceIndicesByFactor(oneMinuteOfIndices, ReductionFactor);
var oneMinuteVector = ContentDescription.ConvertDictionaryToVector(reducedIndices);
var templateVector = ContentDescription.ConvertDictionaryToVector(StrongWindTemplate);

var distance = DataTools.EuclidianDistance(templateVector, oneMinuteVector);

//normalise the distance
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<string, double>(name, 1 - distance);
}

public static Dictionary<string, double[]> GetStrongWindTemplate(Dictionary<string, double[,]> dictionaryOfIndices)
{
var windIndices = ContentDescription.AverageIndicesOverMinutes(dictionaryOfIndices, 23, 27);
var reducedIndices = ContentDescription.ReduceIndicesByFactor(windIndices, ReductionFactor);
return reducedIndices;
}

public static void WriteStrongWindTemplateToFile(Dictionary<string, double[,]> dictionaryOfIndices, string path)
{
var template = WindContent.GetStrongWindTemplate(dictionaryOfIndices);
FileTools.WriteDictionaryToFile(template, path);
}

// #######################################################################################################################

public static KeyValuePair<string, double> GetLightWindContent(Dictionary<string, double[]> oneMinuteOfIndices)
{
const string name = "LightWind1";
var rn = new RandomNumber(DateTime.Now.Second + DateTime.Now.Millisecond + 40);
var score = rn.GetDouble();
return score;
return new KeyValuePair<string, double>(name, score);
}
}
}
18 changes: 18 additions & 0 deletions src/TowseyLibrary/FileTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,24 @@ public static void WriteTextFile(string path, string text)
}// end finally
}

public static void WriteDictionaryToFile(Dictionary<string, double[]> dictionary, string fPath)
{
var text = new StringBuilder();
foreach (KeyValuePair<string, double[]> kvp in dictionary)
{
text.Append(kvp);
var vector = kvp.Value;
for (int i = 0; i < vector.Length; i++)
{
text.Append(string.Format(", {0:F3}", vector[i]));
}

text.Append("\n");
}

WriteTextFile(fPath, text.ToString());
}

public static void Append2TextFile(string fPath, string line)
{
bool saveExistingFile = false;
Expand Down

0 comments on commit d2511f4

Please sign in to comment.