diff --git a/src/AnalysisConfigFiles/Towsey.TemplateDefinitions.json b/src/AnalysisConfigFiles/Towsey.TemplateDefinitions.json
index 5ad026341..6e453a751 100644
--- a/src/AnalysisConfigFiles/Towsey.TemplateDefinitions.json
+++ b/src/AnalysisConfigFiles/Towsey.TemplateDefinitions.json
@@ -213,7 +213,6 @@
]
},
},
- {
{
"Manifest": {
"Name": "WindLight1",
diff --git a/src/AnalysisPrograms/AnalysisPrograms.csproj b/src/AnalysisPrograms/AnalysisPrograms.csproj
index 3e22f9dbc..5c7be8a31 100644
--- a/src/AnalysisPrograms/AnalysisPrograms.csproj
+++ b/src/AnalysisPrograms/AnalysisPrograms.csproj
@@ -302,7 +302,7 @@
AssemblyMetadata.cs
-
+
diff --git a/src/AnalysisPrograms/ContentDescription.cs b/src/AnalysisPrograms/ContentDescription/ContentDescription.cs
similarity index 84%
rename from src/AnalysisPrograms/ContentDescription.cs
rename to src/AnalysisPrograms/ContentDescription/ContentDescription.cs
index f6b9182d8..607e4bba7 100644
--- a/src/AnalysisPrograms/ContentDescription.cs
+++ b/src/AnalysisPrograms/ContentDescription/ContentDescription.cs
@@ -45,15 +45,33 @@ public class UseModel : AbstractStrongAnalyser
public override string Identifier => TowseyContentDescription;
- public override string Description => "[BETA] Generates six spectral indices for Content Description.";
+ public override string Description => "[BETA] Generates six spectral indices used as acoustic features to do Content Description.";
public override AnalysisSettings DefaultSettings => new AnalysisSettings
{
AnalysisTargetSampleRate = ContentSignatures.SampleRate,
};
+ private FunctionalTemplate[] functionalTemplates = null;
+
+ private Dictionary> templatesAsDictionary = null;
+
public override void BeforeAnalyze(AnalysisSettings analysisSettings)
{
+ // Read in the functional templates file. These doe the content description.
+ var cdConfiguration = (CdConfig)analysisSettings.Configuration;
+ var cdConfigFile = analysisSettings.ConfigFile;
+ var configDirectory = cdConfigFile.DirectoryName ?? throw new ArgumentNullException(nameof(cdConfigFile), "Null value");
+ var templatesFileName = cdConfiguration.TemplatesList;
+ var templatesFile = new FileInfo(Path.Combine(configDirectory, templatesFileName));
+ this.functionalTemplates = Json.Deserialize(templatesFile);
+ if (this.functionalTemplates == null)
+ {
+ throw new NullReferenceException(message: $"Array of functional templates was not read correctly from file: <{templatesFile}>");
+ }
+
+ // extract the template definitions as a dictionary. Each definition is itself a dictionary.
+ this.templatesAsDictionary = DataProcessing.ExtractDictionaryOfTemplateDictionaries(this.functionalTemplates);
}
public override AnalyzerConfig ParseConfig(FileInfo file) => ConfigFile.Deserialize(file);
@@ -70,10 +88,20 @@ public class CdConfig : AnalyzerConfig
}
///
- /// This method calls IndexCalculateSixOnly.Analysis() to do the work!.
+ /// This method calls IndexCalculateSixOnly.Analysis() to calculate six spectral indices
+ /// and then calls ContentSignatures.AnalyzeOneMinute() to obtain a content description derived from those indices and an array of functional templates.
///
public override AnalysisResult2 Analyze(AnalysisSettings analysisSettings, SegmentSettings segmentSettings)
{
+ // set the start time for the current recording segment. Default is zero.
+ var elapsedTimeAtStartOfRecording = TimeSpan.Zero;
+ if (segmentSettings.SegmentStartOffset != null)
+ {
+ elapsedTimeAtStartOfRecording = (TimeSpan)segmentSettings.SegmentStartOffset;
+ }
+
+ var startMinuteId = (int)Math.Round(elapsedTimeAtStartOfRecording.TotalMinutes);
+
var audioFile = segmentSettings.SegmentAudioFile;
var recording = new AudioRecording(audioFile.FullName);
@@ -85,9 +113,25 @@ public override AnalysisResult2 Analyze(AnalysisSettings analysisSettings, Se
segmentResults.SpectralIndexValues.FileName = segmentSettings.Segment.SourceMetadata.Identifier;
- // TODO: calculate description results here
- DescriptionResult description = null; // e.g. GetContentDescription();
+ // DO THE CONTENT DESCRIPTION FOR ONE MINUTE HERE
+ // First get acoustic indices for one minute, convert to Dictionary and normalise the values.
+ var indicesDictionary = IndexCalculateSixOnly.ConvertIndicesToDictionary(segmentResults.SpectralIndexValues);
+ 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 to get content description
+ var descriptionResultForOneMinute = ContentSignatures.AnalyzeOneMinute(
+ this.functionalTemplates,
+ this.templatesAsDictionary,
+ indicesDictionary,
+ startMinuteId);
+
+ // set up the analysis results to return
var analysisResults = new AnalysisResult2(analysisSettings, segmentSettings, recording.Duration)
{
AnalysisIdentifier = this.Identifier,
@@ -99,7 +143,7 @@ public override AnalysisResult2 Analyze(AnalysisSettings analysisSettings, Se
},
MiscellaneousResults =
{
- { nameof(DescriptionResult), description },
+ { nameof(DescriptionResult), descriptionResultForOneMinute },
},
};
@@ -164,13 +208,6 @@ public override void SummariseResults(
throw new NullReferenceException();
}
- // set the start time for the current recording segment. Default is zero.
- var startTimeOffset = TimeSpan.Zero;
- if (inputFileSegment.SegmentStartOffset != null)
- {
- startTimeOffset = (TimeSpan)inputFileSegment.SegmentStartOffset;
- }
-
// output config data to disk so other analyzers can use the data,
// Should contain data only - i.e. the configuration settings that generated these indices
// this data can then be used by later analysis processes.
@@ -199,9 +236,6 @@ public override void SummariseResults(
Json.Serialise(icdPath.ToFileInfo(), indexConfigData);
// gather spectra to form spectrograms. Assume same spectra in all analyzer results
- // this is the most efficient way to do this
- // gather up numbers and strings store in memory, write to disk one time
- // this method also AUTOMATICALLY SORTS because it uses array indexing
var dictionaryOfSpectra = spectralIndices.ToTwoDimensionalArray(SpectralIndexValues.CachedSelectors, TwoDimensionalArray.Rotate90ClockWise);
// Calculate the index distribution statistics and write to a json file. Also save as png image
@@ -217,19 +251,16 @@ public override void SummariseResults(
basename: basename,
indexSpectrograms: dictionaryOfSpectra);
- // now get the content description for each minute.
- var templatesFileName = cdConfiguration.TemplatesList;
- var templatesFile = new FileInfo(Path.Combine(configDirectory, templatesFileName));
-
- var contentDictionary = GetContentDescription(spectralIndices, templatesFile, startTimeOffset);
- // TODO: use this when calculating content description results in the Analyze method
- //var allContentDescriptionResults = results.Select(x => (DescriptionResult)x.MiscellaneousResults[nameof(DescriptionResult)]);
+ // Gather the content description results into an array of DescriptionResult and then convert to dictionary
+ var allContentDescriptionResults = results.Select(x => (DescriptionResult)x.MiscellaneousResults[nameof(DescriptionResult)]);
+ var contentDictionary = DataProcessing.ConvertResultsToDictionaryOfArrays(allContentDescriptionResults.ToList());
// Write the results to a csv file
var filePath = Path.Combine(resultsDirectory.FullName, "AcousticSignatures.csv");
FileTools.WriteDictionaryAsCsvFile(contentDictionary, filePath);
- // TODO: use this when calculating content description results in the Analyze method
- //Csv.WriteToCsv(filePath, allContentDescriptionResults);
+
+ // TODO: fix this so it writes header and a column of content description values.
+ //Csv.WriteToCsv(new FileInfo(filePath), contentDictionary);
// prepare graphical plots of the acoustic signatures.
var contentPlots = GetPlots(contentDictionary);
@@ -245,6 +276,7 @@ public override void SummariseResults(
}
///
+ /// NOTE: THIS METHOD SHOULD EVENTUALLY BE DELETED. NO LONGER CALLED.
/// Calculate the content description for each minute.
///
/// set of spectral indices for each minute.
diff --git a/src/AudioAnalysisTools/AudioAnalysisTools.csproj b/src/AudioAnalysisTools/AudioAnalysisTools.csproj
index 9ca1007f2..ed84a9e47 100644
--- a/src/AudioAnalysisTools/AudioAnalysisTools.csproj
+++ b/src/AudioAnalysisTools/AudioAnalysisTools.csproj
@@ -246,6 +246,7 @@
+
diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs b/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs
index b59267332..ab0bb53fd 100644
--- a/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs
+++ b/src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs
@@ -361,12 +361,19 @@ public static double[] GetFreqBinVector(Dictionary dictionary,
return list.ToArray();
}
+ public static Dictionary ConvertResultsToDictionaryOfArrays(List results)
+ {
+ int arrayLength = results.Count;
+ int arrayStart = 0;
+ return ConvertResultsToDictionaryOfArrays(results, arrayLength, arrayStart);
+ }
+
///
- /// 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 for one day.
- /// time start.
+ /// 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 for one day.
+ /// time start.
public static Dictionary ConvertResultsToDictionaryOfArrays(List results, int arrayLength, int arrayStart)
{
var arrays = new Dictionary();
diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/TemplateManifest.cs b/src/AudioAnalysisTools/ContentDescriptionTools/TemplateManifest.cs
index 061100c22..1deca3bf6 100644
--- a/src/AudioAnalysisTools/ContentDescriptionTools/TemplateManifest.cs
+++ b/src/AudioAnalysisTools/ContentDescriptionTools/TemplateManifest.cs
@@ -25,7 +25,7 @@ namespace AudioAnalysisTools.ContentDescriptionTools
public enum EditStatus
{
Edit, // This option edits an existing functional template in the json file. The template definition is (re)calculated.
- Copy, // This option keeps an existing functional template unchanged except for its template Id. Template Ids are assigned in order they appear in the yml file.
+ Copy, // This option keeps an existing functional template unchanged.
Ignore, // This option keeps an existing functional template unchanged except changes its UseStatus boolean field to FALSE.
}
@@ -60,7 +60,6 @@ public static void CreateNewFileOfTemplateDefinitions(FileInfo manifestFile, Fil
{
// the current manifest is not an existing template - therefore make it.
var newTemplate = CreateNewTemplateFromManifest(manifest);
- newTemplate.TemplateId = i;
newTemplate.Template = CreateTemplateDefinition(manifest);
newTemplate.MostRecentEdit = DateTime.Now;
newTemplateList.Add(newTemplate);
@@ -71,7 +70,6 @@ public static void CreateNewFileOfTemplateDefinitions(FileInfo manifestFile, Fil
{
// This option edits an existing functional template in the json file. The template definition is (re)calculated.
var newTemplate = CreateNewTemplateFromManifest(manifest);
- newTemplate.TemplateId = i;
newTemplate.Template = CreateTemplateDefinition(manifest);
newTemplate.MostRecentEdit = DateTime.Now;
newTemplateList.Add(newTemplate);
@@ -82,10 +80,8 @@ public static void CreateNewFileOfTemplateDefinitions(FileInfo manifestFile, Fil
{
// TODO: intentionally broken. FunctionalTemplates should be immutable. If they need to change create a new one (could be a copy, but it would have a version or edit date etc...).
throw new NotImplementedException();
- // This option keeps an existing functional template unchanged except for its template Id.
- // Template ids are assigned in order they appear in the yml file.
+ // This option keeps an existing functional template unchanged.
//var existingTemplate = dictionaryOfCurrentTemplates[name];
- //existingTemplate.TemplateId = i;
//existingTemplate.UseStatus = true;
//existingTemplate.Provenance = null;
//newTemplateList.Add(existingTemplate);
@@ -99,7 +95,6 @@ public static void CreateNewFileOfTemplateDefinitions(FileInfo manifestFile, Fil
throw new NotImplementedException();
// This option keeps an existing functional template unchanged except changes its UseStatus boolean field to FALSE.
//var existingTemplate = dictionaryOfCurrentTemplates[name];
- //existingTemplate.TemplateId = i;
//existingTemplate.Provenance = null;
//existingTemplate.UseStatus = false;
//newTemplateList.Add(existingTemplate);
@@ -281,9 +276,6 @@ public class FunctionalTemplate
{
public TemplateManifest Manifest { get; set; }
- // TODO: I'd suggest removing this concept. Integer based IDs are brittle.
- public int TemplateId { get; set; }
-
///
/// Gets or sets a value indicating whether to use the template or not.
/// UseStatus can be true or false.