diff --git a/src/AnalysisBase/ResultBases/SpectralIndexBase.cs b/src/AnalysisBase/ResultBases/SpectralIndexBase.cs index 50d568d5d..5f2f87161 100644 --- a/src/AnalysisBase/ResultBases/SpectralIndexBase.cs +++ b/src/AnalysisBase/ResultBases/SpectralIndexBase.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // 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). // @@ -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> GetSelectors(); - } + protected static (Dictionary> CachedSelectors, Dictionary> CachedSetters, string[] Keys) MakeSelectors() + where T : SpectralIndexBase + { + var getters = ReflectionExtensions.GetGetters(); + + var cachedSelectors = new Dictionary>(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(); + + var cachedSetters = new Dictionary>(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); + } + } } \ No newline at end of file diff --git a/src/AnalysisPrograms/ContentDescription/UseModel.cs b/src/AnalysisPrograms/ContentDescription/UseModel.cs index 55ab2ecc3..b89d35949 100644 --- a/src/AnalysisPrograms/ContentDescription/UseModel.cs +++ b/src/AnalysisPrograms/ContentDescription/UseModel.cs @@ -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. /// public class UseModel : AbstractStrongAnalyser { @@ -41,6 +41,10 @@ public class UseModel : AbstractStrongAnalyser private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private FunctionalTemplate[] functionalTemplates; + + private Dictionary> templatesAsDictionary; + public override string DisplayName => "Content Description"; public override string Identifier => TowseyContentDescription; @@ -52,10 +56,6 @@ public class UseModel : AbstractStrongAnalyser AnalysisTargetSampleRate = ContentSignatures.SampleRate, }; - private FunctionalTemplate[] functionalTemplates; - - private Dictionary> templatesAsDictionary; - public override void BeforeAnalyze(AnalysisSettings analysisSettings) { // Read in the functional templates file. These doe the content description. @@ -107,11 +107,10 @@ public override AnalysisResult2 Analyze(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]; @@ -124,7 +123,7 @@ public override AnalysisResult2 Analyze(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 @@ -135,7 +134,7 @@ public override AnalysisResult2 Analyze(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 = { @@ -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); @@ -272,61 +271,6 @@ public override void SummariseResults( image.Save(path3); } - /* - /// - /// NOTE: THIS METHOD SHOULD EVENTUALLY BE DELETED. NO LONGER CALLED. - /// Calculate the content description for each minute. - /// - /// set of spectral indices for each minute. - /// json file containing description of templates. - /// Offset into the recording where recording begun (may not be a whole or round minute). - private static Dictionary GetContentDescription( - SpectralIndexBase[] spectralIndices, - FileInfo templatesFile, - TimeSpan elapsedTimeAtStartOfRecording) - { - // Read in the content description templates - var templates = Json.Deserialize(templatesFile); - var templatesAsDictionary = DataProcessing.ExtractDictionaryOfTemplateDictionaries(templates); - var startMinuteId = (int)Math.Round(elapsedTimeAtStartOfRecording.TotalMinutes); - - // create dictionary of index vectors - var results = new List(); - - 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; - } - */ - /// /// Produce plots for graphical display. /// NOTE: The threshold can be changed later. diff --git a/src/AudioAnalysisTools/AudioAnalysisTools.csproj b/src/AudioAnalysisTools/AudioAnalysisTools.csproj index ed84a9e47..cd015acdd 100644 --- a/src/AudioAnalysisTools/AudioAnalysisTools.csproj +++ b/src/AudioAnalysisTools/AudioAnalysisTools.csproj @@ -246,7 +246,7 @@ - + diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs b/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs index 0da6a212e..669b36141 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/ContentSignatures.cs @@ -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; /// /// 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. /// public class ContentSignatures @@ -43,12 +43,13 @@ public class ContentSignatures /// /// Gets an array of six spectral indices that are calculated. /// - public static string[] IndexNames { get; } = { "ACI", "ENT", "EVN", "BGN", "PMN", "OSC" }; + public static string[] IndexNames { get; } = SpectralIndexValuesForContentDescription.Keys; /// /// 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 /// public static string[] UnusedIndexNames { get; } = { "CVR", "DIF", "RHZ", "RVT", "RPS", "RNG", "R3D", "SPT", "SUM" }; diff --git a/src/AudioAnalysisTools/ContentDescriptionTools/SpectralIndexValuesforContentDescription.cs b/src/AudioAnalysisTools/ContentDescriptionTools/SpectralIndexValuesforContentDescription.cs index dd99e5e7d..3961bd113 100644 --- a/src/AudioAnalysisTools/ContentDescriptionTools/SpectralIndexValuesforContentDescription.cs +++ b/src/AudioAnalysisTools/ContentDescriptionTools/SpectralIndexValuesforContentDescription.cs @@ -1,4 +1,4 @@ -// +// // 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). // @@ -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. /// - public class SpectralIndexValuesforContentDescription : ResultBase + public class SpectralIndexValuesForContentDescription : SpectralIndexBase { - //static SpectralIndexValuesforContentDescription() - //{ - // var getters = ReflectionExtensions.GetGetters(); - - // CachedSelectors = new Dictionary>(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(); - - // CachedSetters = new Dictionary>(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 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; - //} - - /// - /// 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). - /// - /// - /// The dictionary to convert to spectral index base - /// - public static SpectralIndexValues[] ImportFromDictionary(Dictionary dictionaryOfSpectra) - { - return dictionaryOfSpectra.FromTwoDimensionalArray(CachedSetters, TwoDimensionalArray.Rotate90AntiClockWise); + var result = MakeSelectors(); + CachedSelectors = result.CachedSelectors; + CachedSetters = result.CachedSetters; + Keys = result.Keys; } - /// - /// 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. - /// - public static void CheckExistenceOfSpectralIndexValues(Dictionary 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> CachedSelectors { get; } - public static Dictionary> CachedSetters { get; } + public static Dictionary> CachedSetters { get; } public static string[] Keys { get; } @@ -141,15 +57,7 @@ public static Image CreateImageOfSpectralIndices(SpectralIndexValues spectralInd var combinedImage = ImageTools.CombineImagesVertically(images.ToArray()); return combinedImage; } - - /// - /// Gets the configuration used to generate these results. - /// - /// - /// This property was added when we started generating lots of results that used - /// different parameters - we needed a way to disambiguate them. - /// - public IndexCalculateConfig Configuration { get; } + // 1: public double[] ACI { get; set; } @@ -175,9 +83,6 @@ public static Image CreateImageOfSpectralIndices(SpectralIndexValues spectralInd /// public double[] PMN { get; set; } - //public override Dictionary> GetSelectors() - //{ - // return CachedSelectors; - //} + public override Dictionary> GetSelectors() => CachedSelectors; } } \ No newline at end of file diff --git a/src/AudioAnalysisTools/Indices/IndexCalculateSixOnly.cs b/src/AudioAnalysisTools/Indices/IndexCalculateSixOnly.cs index 89c14646c..0f6c1373e 100644 --- a/src/AudioAnalysisTools/Indices/IndexCalculateSixOnly.cs +++ b/src/AudioAnalysisTools/Indices/IndexCalculateSixOnly.cs @@ -41,7 +41,8 @@ public class IndexCalculateSixOnly /// boolean with default value = false. /// An IndexCalculateResult. [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] - public static IndexCalculateResult Analysis( + //////public static IndexCalculateResult Analysis( + public static SpectralIndexValuesForContentDescription Analysis( AudioRecording recording, TimeSpan segmentOffsetTimeSpan, int sampleRateOfOriginalAudioFile, @@ -66,16 +67,16 @@ public static IndexCalculateResult Analysis( var config = new IndexCalculateConfig(); // sets some default values int freqBinCount = frameSize / 2; var indexProperties = GetIndexProperties(); - var result = new IndexCalculateResult(freqBinCount, indexProperties, indexCalculationDuration, segmentOffsetTimeSpan, config); - //var result = new SpectralIndexValuesforContentDescription(); + ////////var result = new IndexCalculateResult(freqBinCount, indexProperties, indexCalculationDuration, segmentOffsetTimeSpan, config); + var spectralIndices = new SpectralIndexValuesForContentDescription(); - //result.SummaryIndexValues = null; - SpectralIndexValues spectralIndices = result.SpectralIndexValues; + ///////result.SummaryIndexValues = null; + ///////SpectralIndexValues spectralIndices = result.SpectralIndexValues; // set up default spectrogram to return - result.Sg = returnSonogramInfo ? GetSonogram(recording, windowSize: 1024) : null; - result.Hits = null; - result.TrackScores = new List(); + ///////result.Sg = returnSonogramInfo ? GetSonogram(recording, windowSize: 1024) : null; + ///////result.Hits = null; + ///////result.TrackScores = new List(); // ################################## FINISHED SET-UP // ################################## NOW GET THE AMPLITUDE SPECTROGRAM @@ -154,8 +155,9 @@ public static IndexCalculateResult Analysis( //spectralIndices.CVR = spActivity.CoverSpectrum; spectralIndices.EVN = spActivity.EventSpectrum; - result.TrackScores = null; - return result; + ///////result.TrackScores = null; + ///////return result; + return spectralIndices; } // end calculation of Six Spectral Indices public static Dictionary GetIndexProperties() @@ -183,7 +185,7 @@ public static Dictionary GetIndexProperties() /// Transfers the required six indices from SpectralIndexBase to a dictionary. /// IMPORTANT NOTE: THis method needs to be updated if there is a change to the indices used for content description. /// - public static Dictionary ConvertIndicesToDictionary(SpectralIndexBase indexSet) + /*public static Dictionary ConvertIndicesToDictionary(SpectralIndexBase indexSet) { var dictionary = new Dictionary(); var aciArray = (double[])indexSet.GetPropertyValue("ACI"); @@ -199,7 +201,7 @@ public static Dictionary ConvertIndicesToDictionary(SpectralIn var oscArray = (double[])indexSet.GetPropertyValue("OSC"); dictionary.Add("OSC", oscArray); return dictionary; - } + }*/ private static SpectrogramStandard GetSonogram(AudioRecording recording, int windowSize) { diff --git a/src/AudioAnalysisTools/Indices/SpectralIndexValues.cs b/src/AudioAnalysisTools/Indices/SpectralIndexValues.cs index e11c0d057..f440eb85e 100644 --- a/src/AudioAnalysisTools/Indices/SpectralIndexValues.cs +++ b/src/AudioAnalysisTools/Indices/SpectralIndexValues.cs @@ -15,35 +15,14 @@ namespace AudioAnalysisTools.Indices public class SpectralIndexValues : SpectralIndexBase { + // Static constructors are called implicitly when the type is first used. + // Do NOT delete even if it has 0 references. static SpectralIndexValues() { - var getters = ReflectionExtensions.GetGetters(); - - CachedSelectors = new Dictionary>(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(); - - CachedSetters = new Dictionary>(getters.Count); - foreach (var keyValuePair in setters) - { - // var key = keyValuePair.Key; - var setter = keyValuePair.Value; - - CachedSetters.Add( - keyValuePair.Key, - (spectrumBase, value) => setter(spectrumBase, value)); - } + var result = MakeSelectors(); + CachedSelectors = result.CachedSelectors; + CachedSetters = result.CachedSetters; + Keys = result.Keys; } public SpectralIndexValues() @@ -196,9 +175,6 @@ public static Image CreateImageOfSpectralIndices(SpectralIndexValues spectralInd public double[] SUM { get; set; } - public override Dictionary> GetSelectors() - { - return CachedSelectors; - } + public override Dictionary> GetSelectors() => CachedSelectors; } } \ No newline at end of file