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.