Skip to content

Commit

Permalink
Set up two new content types, Morning Chorus and Silver Eye
Browse files Browse the repository at this point in the history
Issue #252 Set up two new conetent type classes and refactor other classes
  • Loading branch information
towsey committed Sep 13, 2019
1 parent 2e4c4c1 commit 45aeb8b
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 121 deletions.
29 changes: 16 additions & 13 deletions src/AnalysisPrograms/Sandpit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -188,24 +187,28 @@ 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);

// 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),
//};

//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);
}

Expand Down
3 changes: 2 additions & 1 deletion src/AudioAnalysisTools/AudioAnalysisTools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@
<Compile Include="ContentDescriptionTools\ContentDescription.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\BaseContentType.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\RainLight1.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\RainHeavy2.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\BirdMorningChorus1.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\SilverEyeMezTasmanIs.cs" />
<Compile Include="ContentDescriptionTools\ContentTypes\WindLight1.cs" />
<Compile Include="ContentDescriptionTools\ContentVisualization.cs" />
<Compile Include="ContentDescriptionTools\DescriptionResult.cs" />
Expand Down
144 changes: 135 additions & 9 deletions src/AudioAnalysisTools/ContentDescriptionTools/ContentDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// The following min and max bounds are same as those defined in the IndexPropertiesConfig.yml file as of August 2019.
/// </summary>
Expand All @@ -44,20 +43,22 @@ public static List<Plot> 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;
}

Expand Down Expand Up @@ -176,6 +177,8 @@ public static List<DescriptionResult> AnalyseMinutes(Dictionary<string, double[,
descriptionResult.AddDescription(WindStrong1.GetContent(oneMinuteOfIndices));
descriptionResult.AddDescription(WindLight1.GetContent(oneMinuteOfIndices));
descriptionResult.AddDescription(RainLight1.GetContent(oneMinuteOfIndices));
descriptionResult.AddDescription(BirdMorningChorus1.GetContent(oneMinuteOfIndices));
descriptionResult.AddDescription(SilverEyeMezTasmanIs.GetContent(oneMinuteOfIndices));

// yet to do following
//descriptionResult.AddDescription(RainHeavy1.GetContent(oneMinuteOfIndices));
Expand Down Expand Up @@ -246,6 +249,72 @@ public static Dictionary<string, double[]> ReduceIndicesByFactor(Dictionary<stri
return opIndices;
}

/// <summary>
/// Returns the bin bounds assuming that the full spectrum consists of the defaul value = 256.
/// </summary>
/// <param name="bottomFrequency">Units = Hertz.</param>
/// <param name="topFrequency">Hertz.</param>
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<string, double[]> ApplyBandPass(Dictionary<string, double[]> indices, int bottomBin, int topBin)
{
int length = topBin - bottomBin + 1;
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.Subarray(vector, bottomBin, length);
opIndices.Add(key, opVector);
}
}

return opIndices;
}

/// <summary>
/// THis method assumes that the passed temp[late contains only one value for each key.
/// </summary>
/// <param name="templateDict"> Each kvp = string, double.</param>
/// <param name="oneMinuteIndices">the indices.</param>
/// <returns>A spectrum of similarity-distance scores.</returns>
public static double[] ScanSpectrumWithTemplate(Dictionary<string, double[]> templateDict, Dictionary<string, double[]> 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<string, double[]> dictionary)
{
var list = new List<double>();
Expand All @@ -262,6 +331,22 @@ public static double[] ConvertDictionaryToVector(Dictionary<string, double[]> di
return list.ToArray();
}

public static double[] GetFreqBinVector(Dictionary<string, double[]> dictionary, int id)
{
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.Add(indices[id]);
}
}

return list.ToArray();
}

public static Dictionary<string, Plot> ConvertResultsToPlots(List<DescriptionResult> results, int plotLength, int plotStart)
{
var plots = new Dictionary<string, Plot>();
Expand Down Expand Up @@ -324,6 +409,47 @@ public static List<Plot> SubtractMeanPlusSd(List<Plot> plots)
return opPlots;
}

/// <summary>
/// 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.
/// </summary>
public static List<Plot> SubtractModeAndSd(List<Plot> plots)
{
var opPlots = new List<Plot>();

// 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<Plot> ConvertPlotDictionaryToPlotList(Dictionary<string, Plot> dict)
{
var list = new List<Plot>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, double[]> GetTemplate(Dictionary<string, double[,]> 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<string, double[]> SilverEyeTemplate = new Dictionary<string, double[]>
{
["ACI"] = new[] { 0.779 },
["ENT"] = new[] { 0.393 },
["EVN"] = new[] { 0.686 },
["BGN"] = new[] { 0.085 },
["PMN"] = new[] { 0.883 },
};

public static KeyValuePair<string, double> GetContent(Dictionary<string, double[]> 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<string, double>(Name, score);
}

public static Dictionary<string, double[]> 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<string, double[,]> 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
Expand Down
Loading

0 comments on commit 45aeb8b

Please sign in to comment.