Skip to content

Commit

Permalink
Finally get content description working in IAnalyzer2 environment.
Browse files Browse the repository at this point in the history
Issue #252 Finally get content description working in IAnalyzer2 environment.
  • Loading branch information
towsey committed Oct 22, 2019
1 parent 516abf0 commit 9c13b79
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 46 deletions.
52 changes: 37 additions & 15 deletions src/AnalysisPrograms/ContentDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -222,7 +225,10 @@ public override void SummariseResults(
/// <param name="spectralIndices">set of spectral indices for each minute.</param>
/// <param name="templatesFile">json file containing description of templates.</param>
/// <param name="elapsedTimeAtStartOfRecording">minute Id for start of recording.</param>
public static List<Plot> GetContentDescription(SpectralIndexBase[] spectralIndices, FileInfo templatesFile, TimeSpan elapsedTimeAtStartOfRecording)
public static Dictionary<string, double[]> GetContentDescription(
SpectralIndexBase[] spectralIndices,
FileInfo templatesFile,
TimeSpan elapsedTimeAtStartOfRecording)
{
// Read in the content description templates
var templates = Json.Deserialize<TemplateManifest[]>(templatesFile);
Expand All @@ -242,6 +248,15 @@ public static List<Plot> 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,
Expand All @@ -251,14 +266,21 @@ public static List<Plot> 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;
}

/// <summary>
/// Produce plots for graphical display.
/// NOTE: The threshold can be changed later.
/// </summary>
/// <returns>A list of graphical plots.</returns>
private static List<Plot> GetPlots(Dictionary<string, double[]> contentDictionary)
{
double threshold = 0.25;
var plotDict = DataProcessing.ConvertArraysToPlots(contentDictionary, threshold);
var contentPlots = DataProcessing.ConvertPlotDictionaryToPlotList(plotDict);

// convert scores to z-scores
Expand Down
6 changes: 3 additions & 3 deletions src/AnalysisPrograms/Sandpit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
Expand Down Expand Up @@ -50,8 +50,11 @@ public class ContentSignatures
/// <returns>A list of plots - each plot is the minute by minute scores for a single template.</returns>
public static List<Plot> ContentDescriptionOfMultipleRecordingFiles(FileInfo listOfIndexFiles, FileInfo templatesFile)
{
// Read in all template manifests
//var templates = Yaml.Deserialize<TemplateManifest[]>(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<TemplateManifest[]>(templatesFile);
var templatesAsDictionary = DataProcessing.ExtractDictionaryOfTemplateDictionaries(templates);

Expand All @@ -67,7 +70,8 @@ public static List<Plot> 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
Expand All @@ -79,11 +83,11 @@ public static List<Plot> 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
Expand Down Expand Up @@ -124,6 +128,14 @@ public static List<DescriptionResult> AnalyzeMinutes(
return results;
}

/// <summary>
/// IMPORTANT: The indices passed in the dictionary "oneMinuteOfIndices" must be normalised.
/// </summary>
/// <param name="templates">The templates read from json file.</param>
/// <param name="templatesAsDictionary">The numerical pasrt of each template.</param>
/// <param name="oneMinuteOfIndices">The normalised values of the indices derived from one minute of recording.</param>
/// <param name="minuteId">The minute ID, i.e. its temporal position.</param>
/// <returns>A single instance of a DescriptionResult.</returns>
public static DescriptionResult AnalyzeOneMinute(
TemplateManifest[] templates,
Dictionary<string, Dictionary<string, double[]>> templatesAsDictionary,
Expand Down
41 changes: 23 additions & 18 deletions src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static Dictionary<string, TemplateManifest> ConvertTemplateArrayToDiction

/// <summary>
/// Reads in all the index matrices whose keys are in the above array of IndexNames.
/// Returns normalised values.
/// </summary>
/// <param name="filePath">Partial path to the index files.</param>
/// <returns>a Dictionary of matrices containing normalised index values.</returns>
Expand Down Expand Up @@ -346,13 +347,13 @@ public static double[] GetFreqBinVector(Dictionary<string, double[]> dictionary,
/// Converts individual results to a dictionary of plots.
/// </summary>
/// <param name="results">a list of results for each content type in every minute.</param>
/// <param name="arrayLength">The plot length will the total number of minutes scanned, typically 1440 or one day.</param>
/// <param name="arrayLength">The plot length will the total number of minutes scanned, typically 1440 for one day.</param>
/// <param name="arrayStart">time start.</param>
public static Dictionary<string, double[]> ConvertResultsToArrays(List<DescriptionResult> results, int arrayLength, int arrayStart)
public static Dictionary<string, double[]> ConvertResultsToDictionaryOfArrays(List<DescriptionResult> results, int arrayLength, int arrayStart)
{
var arrays = new Dictionary<string, double[]>();

foreach (DescriptionResult result in results)
foreach (var result in results)
{
var time = (int)Math.Round(result.StartTimeInCurrentRecordingFile.TotalMinutes);
var dict = result.GetDescriptionDictionary();
Expand Down Expand Up @@ -383,26 +384,30 @@ public static Dictionary<string, double[]> ConvertResultsToArrays(List<Descripti
/// <param name="plotStart">time start.</param>
public static Dictionary<string, Plot> ConvertResultsToPlots(List<DescriptionResult> results, int plotLength, int plotStart)
{
var dictOfArrays = ConvertResultsToDictionaryOfArrays(results, plotLength, plotStart);
var plots = new Dictionary<string, Plot>();

foreach (DescriptionResult result in results)
foreach (var kvp in dictOfArrays)
{
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;
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<string, Plot> ConvertArraysToPlots(Dictionary<string, double[]> dictOfArrays, double threshold)
{
var plots = new Dictionary<string, Plot>();

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;
Expand Down
32 changes: 32 additions & 0 deletions src/TowseyLibrary/FileTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,38 @@ public static void WriteDictionaryToFile(Dictionary<string, double[]> dictionary
WriteTextFile(fPath, text.ToString());
}

/// <summary>
/// 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!.
/// </summary>
/// <param name="dictionary">a dictionary of arrays of double.</param>
/// <param name="fPath">The file path.</param>
public static void WriteDictionaryAsCsvFile(Dictionary<string, double[]> 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<string, double[]> 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;
Expand Down
16 changes: 12 additions & 4 deletions src/TowseyLibrary/Plot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit 9c13b79

Please sign in to comment.