Skip to content

Commit

Permalink
Work done by Anthony to implement a new Results class for 6 indices.
Browse files Browse the repository at this point in the history
Issue #252. Work done by Anthony to implement a new Results class for returning the six spectral indices used for content description.
  • Loading branch information
towsey committed Nov 22, 2019
1 parent 376a503 commit 9ad5c90
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 227 deletions.
37 changes: 33 additions & 4 deletions src/AnalysisBase/ResultBases/SpectralIndexBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// --------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SpectralIndexBase.cs" company="QutEcoacoustics">
// 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).
// </copyright>
Expand All @@ -11,15 +11,44 @@ namespace AnalysisBase.ResultBases
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

public abstract class SpectralIndexBase : ResultBase
{
public abstract Dictionary<string, Func<SpectralIndexBase, double[]>> GetSelectors();

}
protected static (Dictionary<string, Func<SpectralIndexBase, double[]>> CachedSelectors, Dictionary<string, Action<T, double[]>> CachedSetters, string[] Keys) MakeSelectors<T>()
where T : SpectralIndexBase
{
var getters = ReflectionExtensions.GetGetters<T, double[]>();

var cachedSelectors = new Dictionary<string, Func<SpectralIndexBase, double[]>>(getters.Count);
foreach (var keyValuePair in getters)
{
// var key = keyValuePair.Key;
var selector = keyValuePair.Value;

cachedSelectors.Add(
keyValuePair.Key,
spectrumBase => selector((T)spectrumBase));
}

var keys = cachedSelectors.Keys.ToArray();

var setters = ReflectionExtensions.GetSetters<T, double[]>();

var cachedSetters = new Dictionary<string, Action<T, double[]>>(getters.Count);
foreach (var keyValuePair in setters)
{
// var key = keyValuePair.Key;
var setter = keyValuePair.Value;

cachedSetters.Add(
keyValuePair.Key,
(spectrumBase, value) => setter(spectrumBase, value));
}

return (cachedSelectors, cachedSetters, keys);
}
}
}
78 changes: 11 additions & 67 deletions src/AnalysisPrograms/ContentDescription/UseModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ namespace AnalysisPrograms.ContentDescription
/// This class is derived from AbstractStrongAnalyser.
/// It is equivalent to AnalyseLongRecording.cs or a species recognizer.
/// To call this class, the first argument on the commandline must be 'audio2csv'.
/// Given a one-minute recording segment, the ContentDescription.Analyze() method calls AudioAnalysisTools.Indices.IndexCalculateSixOnly.Analysis().
/// Given a one-minute recording segment, the UseModel.Analyze() method calls AudioAnalysisTools.Indices.IndexCalculateSixOnly.Analysis().
/// This calculates six spectral indices, ACI, ENT, EVN, BGN, PMN, OSC. This set of 6x256 acoustic features is used for content description.
/// The content description methods are called from UseModel.SummariseResults() method.
/// The content description methods are called from UseModel.Analyze() method.
/// </summary>
public class UseModel : AbstractStrongAnalyser
{
Expand All @@ -41,6 +41,10 @@ public class UseModel : AbstractStrongAnalyser

private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

private FunctionalTemplate[] functionalTemplates;

private Dictionary<string, Dictionary<string, double[]>> templatesAsDictionary;

public override string DisplayName => "Content Description";

public override string Identifier => TowseyContentDescription;
Expand All @@ -52,10 +56,6 @@ public class UseModel : AbstractStrongAnalyser
AnalysisTargetSampleRate = ContentSignatures.SampleRate,
};

private FunctionalTemplate[] functionalTemplates;

private Dictionary<string, Dictionary<string, double[]>> templatesAsDictionary;

public override void BeforeAnalyze(AnalysisSettings analysisSettings)
{
// Read in the functional templates file. These doe the content description.
Expand Down Expand Up @@ -107,11 +107,10 @@ public override AnalysisResult2 Analyze<T>(AnalysisSettings analysisSettings, Se
segmentSettings.SegmentStartOffset,
segmentSettings.Segment.SourceMetadata.SampleRate);

segmentResults.SpectralIndexValues.FileName = segmentSettings.Segment.SourceMetadata.Identifier;

// DO THE CONTENT DESCRIPTION FOR ONE MINUTE HERE
// First get acoustic indices for one minute, convert to Dictionary and normalize the values.
var indicesDictionary = IndexCalculateSixOnly.ConvertIndicesToDictionary(segmentResults.SpectralIndexValues);
var indicesDictionary = segmentResults.AsArray().ToTwoDimensionalArray(SpectralIndexValuesForContentDescription.CachedSelectors);
//var indicesDictionary = IndexCalculateSixOnly.ConvertIndicesToDictionary(segmentResults);
foreach (string key in ContentSignatures.IndexNames)
{
var indexBounds = ContentSignatures.IndexValueBounds[key];
Expand All @@ -124,7 +123,7 @@ public override AnalysisResult2 Analyze<T>(AnalysisSettings analysisSettings, Se
var descriptionResultForOneMinute = ContentSignatures.AnalyzeOneMinute(
this.functionalTemplates,
this.templatesAsDictionary,
indicesDictionary,
indicesDictionary.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetRow(0)), // this line converts dictionary of one-row matrices to dictionary of arrays.
startMinuteId);

// set up the analysis results to return
Expand All @@ -135,7 +134,7 @@ public override AnalysisResult2 Analyze<T>(AnalysisSettings analysisSettings, Se
{
// Transfer the spectral index results to AnalysisResults
// TODO: consider not returning this value if it is not needed in summarize
segmentResults.SpectralIndexValues,
segmentResults,
},
MiscellaneousResults =
{
Expand Down Expand Up @@ -254,10 +253,10 @@ public override void SummariseResults(

// Write the results to a csv file
var filePath = Path.Combine(resultsDirectory.FullName, "AcousticSignatures.csv");
FileTools.WriteDictionaryAsCsvFile(contentDictionary, filePath);

// TODO: fix this so it writes header and a column of content description values.
//Csv.WriteToCsv(new FileInfo(filePath), contentDictionary);
FileTools.WriteDictionaryAsCsvFile(contentDictionary, filePath);

// prepare graphical plots of the acoustic signatures.
var contentPlots = GetPlots(contentDictionary);
Expand All @@ -272,61 +271,6 @@ public override void SummariseResults(
image.Save(path3);
}

/*
/// <summary>
/// NOTE: THIS METHOD SHOULD EVENTUALLY BE DELETED. NO LONGER CALLED.
/// Calculate the content description for each minute.
/// </summary>
/// <param name="spectralIndices">set of spectral indices for each minute.</param>
/// <param name="templatesFile">json file containing description of templates.</param>
/// <param name="elapsedTimeAtStartOfRecording">Offset into the recording where recording begun (may not be a whole or round minute).</param>
private static Dictionary<string, double[]> GetContentDescription(
SpectralIndexBase[] spectralIndices,
FileInfo templatesFile,
TimeSpan elapsedTimeAtStartOfRecording)
{
// Read in the content description templates
var templates = Json.Deserialize<FunctionalTemplate[]>(templatesFile);
var templatesAsDictionary = DataProcessing.ExtractDictionaryOfTemplateDictionaries(templates);
var startMinuteId = (int)Math.Round(elapsedTimeAtStartOfRecording.TotalMinutes);
// create dictionary of index vectors
var results = new List<DescriptionResult>();
int length = spectralIndices.Length;
//loop over all minutes in the recording
for (int i = 0; i < length; i++)
{
var oneMinuteOfIndices = spectralIndices[i];
// 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,
templatesAsDictionary,
indicesDictionary,
startMinuteId + i);
results.Add(resultsForOneMinute);
}
// convert results to dictionary of score arrays
var dictionaryOfScores = DataProcessing.ConvertResultsToDictionaryOfArrays(results, length, startMinuteId);
return dictionaryOfScores;
}
*/

/// <summary>
/// Produce plots for graphical display.
/// NOTE: The threshold can be changed later.
Expand Down
2 changes: 1 addition & 1 deletion src/AudioAnalysisTools/AudioAnalysisTools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
<Compile Include="ContentDescriptionTools\ContentVisualization.cs" />
<Compile Include="ContentDescriptionTools\DataProcessing.cs" />
<Compile Include="ContentDescriptionTools\DescriptionResult.cs" />
<Compile Include="ContentDescriptionTools\SpectralIndexValuesforContentDescription.cs" />
<Compile Include="ContentDescriptionTools\SpectralIndexValuesForContentDescription.cs" />
<Compile Include="ContentDescriptionTools\TemplateManifest.cs" />
<Compile Include="ContentDescriptionTools\TemplateCollection.cs" />
<Compile Include="CrossCorrelation.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ namespace AudioAnalysisTools.ContentDescriptionTools
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Acoustics.Shared;
using AudioAnalysisTools.Indices;
using TowseyLibrary;

/// <summary>
/// This class contains methods which use functional templates to scan one or multiple files to obtain a content description.
/// For consistency between recordings many parameters such as sample rate, frame size etc, must be declared as constants.
/// In addition, the absolute values in the template description dictionary must be normalised using the fixed set of normalisation bounds in IndexValueBounds.
/// In addition, the absolute values in the template description dictionary must be normalised using the fixed set of normalization bounds in IndexValueBounds.
/// Note that each functional template uses one of a small number of algorithms to calculate a similarity value.
/// </summary>
public class ContentSignatures
Expand Down Expand Up @@ -43,12 +43,13 @@ public class ContentSignatures
/// <summary>
/// Gets an array of six spectral indices that are calculated.
/// </summary>
public static string[] IndexNames { get; } = { "ACI", "ENT", "EVN", "BGN", "PMN", "OSC" };
public static string[] IndexNames { get; } = SpectralIndexValuesForContentDescription.Keys;

/// <summary>
/// Gets an array containing names of spectral indices that are not wanted. They are used to remove unwanted selectors.
/// This is a temporary arrangement to utilize existing code.
/// TODO Eventually separate out template results so do not have to use the AnalysisResult2 class.
/// ToDO: this should now be deleteable
/// </summary>
public static string[] UnusedIndexNames { get; } = { "CVR", "DIF", "RHZ", "RVT", "RPS", "RNG", "R3D", "SPT", "SUM" };

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="SpectralIndexValuesforContentDescription.cs" company="QutEcoacoustics">
// <copyright file="SpectralIndexValuesForContentDescription.cs" company="QutEcoacoustics">
// 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).
// </copyright>

Expand All @@ -19,110 +19,26 @@ namespace AudioAnalysisTools.Indices
/// Purpose of this class is to avoid using the class IndexCalculateResult for returning results from IndexCalculateSixOnly.Analysis();
/// This class is stripped down to just the required six spectral indices.
/// </summary>
public class SpectralIndexValuesforContentDescription : ResultBase
public class SpectralIndexValuesForContentDescription : SpectralIndexBase
{
//static SpectralIndexValuesforContentDescription()
//{
// var getters = ReflectionExtensions.GetGetters<SpectralIndexValues, double[]>();

// CachedSelectors = new Dictionary<string, Func<SpectralIndexBase, double[]>>(getters.Count);
// foreach (var keyValuePair in getters)
// {
// // var key = keyValuePair.Key;
// var selector = keyValuePair.Value;

// CachedSelectors.Add(
// keyValuePair.Key,
// spectrumBase => selector((SpectralIndexValues)spectrumBase));
// }

// Keys = CachedSelectors.Keys.ToArray();

// var setters = ReflectionExtensions.GetSetters<SpectralIndexValues, double[]>();

// CachedSetters = new Dictionary<string, Action<SpectralIndexValues, double[]>>(getters.Count);
// foreach (var keyValuePair in setters)
// {
// // var key = keyValuePair.Key;
// var setter = keyValuePair.Value;

// CachedSetters.Add(
// keyValuePair.Key,
// (spectrumBase, value) => setter(spectrumBase, value));
// }
//}

public SpectralIndexValuesforContentDescription()
// Static constructors are called implicitly when the type is first used.
// Do NOT delete even if it has 0 references.
static SpectralIndexValuesForContentDescription()
{
// empty constructor important!
}

//public SpectralIndexValuesforContentDescription(int spectrumLength, Dictionary<string, IndexProperties> indexProperties, IndexCalculateConfig configuration)
//{
// foreach (var cachedSetter in CachedSetters)
// {
// var defaultValue = 0.0;

// if (indexProperties.ContainsKey(cachedSetter.Key))
// {
// var indexProperty = indexProperties[cachedSetter.Key];
// if (indexProperty.IsSpectralIndex)
// {
// defaultValue = indexProperty.DefaultValue;
// }
// }

// double[] initArray = new double[spectrumLength].FastFill(defaultValue);

// // WARNING: Potential throw site
// // No need to give following warning because should call CheckExistenceOfSpectralIndexValues() method before entering loop.
// // This prevents multiple warnings through loop.
// //this.SetPropertyValue(cachedSetter.Key, initArray);

// cachedSetter.Value(this, initArray);
// }

// this.Configuration = configuration;
//}

/// <summary>
/// Imports a dictionary of spectra.
/// Assumes `CheckExistenceOfSpectralIndexValues` has already been called.
/// Assumes frequency component is in fist index (i.e. frequency is rows) and time in second index (time is columns).
/// </summary>
/// <param name="dictionaryOfSpectra">
/// The dictionary to convert to spectral index base
/// </param>
public static SpectralIndexValues[] ImportFromDictionary(Dictionary<string, double[,]> dictionaryOfSpectra)
{
return dictionaryOfSpectra.FromTwoDimensionalArray(CachedSetters, TwoDimensionalArray.Rotate90AntiClockWise);
var result = MakeSelectors<SpectralIndexValuesForContentDescription>();
CachedSelectors = result.CachedSelectors;
CachedSetters = result.CachedSetters;
Keys = result.Keys;
}

/// <summary>
/// Used to check that the keys in the indexProperties dictionary correspond to Properties in the SpectralIndexValues class.
/// Call this method before entering a loop because do not want the error message at every iteration through loop.
/// </summary>
public static void CheckExistenceOfSpectralIndexValues(Dictionary<string, IndexProperties> indexProperties)
public SpectralIndexValuesForContentDescription()
{
foreach (var kvp in indexProperties)
{
if (!kvp.Value.IsSpectralIndex)
{
continue;
}

var success = CachedSelectors.ContainsKey(kvp.Key);
if (!success)
{
LoggedConsole.WriteWarnLine(
"### WARNING: The PROPERTY <" + kvp.Key + "> does not exist in the SpectralIndexValues class!");
}
}
// empty constructor important!
}

public static Dictionary<string, Func<SpectralIndexBase, double[]>> CachedSelectors { get; }

public static Dictionary<string, Action<SpectralIndexValues, double[]>> CachedSetters { get; }
public static Dictionary<string, Action<SpectralIndexValuesForContentDescription, double[]>> CachedSetters { get; }

public static string[] Keys { get; }

Expand All @@ -141,15 +57,7 @@ public static Image CreateImageOfSpectralIndices(SpectralIndexValues spectralInd
var combinedImage = ImageTools.CombineImagesVertically(images.ToArray());
return combinedImage;
}

/// <summary>
/// Gets the configuration used to generate these results.
/// </summary>
/// <remarks>
/// This property was added when we started generating lots of results that used
/// different parameters - we needed a way to disambiguate them.
/// </remarks>
public IndexCalculateConfig Configuration { get; }


// 1:
public double[] ACI { get; set; }
Expand All @@ -175,9 +83,6 @@ public static Image CreateImageOfSpectralIndices(SpectralIndexValues spectralInd
/// </summary>
public double[] PMN { get; set; }

//public override Dictionary<string, Func<SpectralIndexBase, double[]>> GetSelectors()
//{
// return CachedSelectors;
//}
public override Dictionary<string, Func<SpectralIndexBase, double[]>> GetSelectors() => CachedSelectors;
}
}
Loading

0 comments on commit 9ad5c90

Please sign in to comment.