Skip to content

Commit

Permalink
Shift Content description analysis to main analysis loop
Browse files Browse the repository at this point in the history
Issue #252 Shift the calculation of content description from method SummarizeResults() to the actual Analysis loop. This is possible because the content description for each minute is independent of other minutes.
  • Loading branch information
towsey committed Nov 14, 2019
1 parent 79f939b commit c342e0b
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 41 deletions.
1 change: 0 additions & 1 deletion src/AnalysisConfigFiles/Towsey.TemplateDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@
]
},
},
{
{
"Manifest": {
"Name": "WindLight1",
Expand Down
2 changes: 1 addition & 1 deletion src/AnalysisPrograms/AnalysisPrograms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@
<Compile Include="..\AssemblyMetadata.cs">
<Link>AssemblyMetadata.cs</Link>
</Compile>
<Compile Include="ContentDescription.cs" />
<Compile Include="ContentDescription\ContentDescription.cs" />
<Compile Include="AcousticWorkbench.Orchestration\EventMetadataResolver.cs" />
<Compile Include="AcousticWorkbench.Orchestration\RemoteSegment.cs" />
<Compile Include="AED.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Dictionary<string, double[]>> 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<FunctionalTemplate[]>(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<CdConfig>(file);
Expand All @@ -70,10 +88,20 @@ public class CdConfig : AnalyzerConfig
}

/// <summary>
/// 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.
/// </summary>
public override AnalysisResult2 Analyze<T>(AnalysisSettings analysisSettings, SegmentSettings<T> 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);

Expand All @@ -85,9 +113,25 @@ public override AnalysisResult2 Analyze<T>(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,
Expand All @@ -99,7 +143,7 @@ public override AnalysisResult2 Analyze<T>(AnalysisSettings analysisSettings, Se
},
MiscellaneousResults =
{
{ nameof(DescriptionResult), description },
{ nameof(DescriptionResult), descriptionResultForOneMinute },
},
};

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -245,6 +276,7 @@ public override void SummariseResults(
}

/// <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>
Expand Down
1 change: 1 addition & 0 deletions src/AudioAnalysisTools/AudioAnalysisTools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +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\TemplateManifest.cs" />
<Compile Include="ContentDescriptionTools\TemplateCollection.cs" />
<Compile Include="CrossCorrelation.cs" />
Expand Down
17 changes: 12 additions & 5 deletions src/AudioAnalysisTools/ContentDescriptionTools/DataProcessing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,19 @@ public static double[] GetFreqBinVector(Dictionary<string, double[]> dictionary,
return list.ToArray();
}

public static Dictionary<string, double[]> ConvertResultsToDictionaryOfArrays(List<DescriptionResult> results)
{
int arrayLength = results.Count;
int arrayStart = 0;
return ConvertResultsToDictionaryOfArrays(results, arrayLength, arrayStart);
}

/// <summary>
/// Converts individual results to a dictionary of plots.
/// </summary>
/// <param name="results">a list of results for each content type in every minute.</param>
/// <param name="arrayLength">The plot length will the total number of minutes scanned, typically 1440 for one day.</param>
/// <param name="arrayStart">time start.</param>
/// Converts individual results to a dictionary of plots.
/// </summary>
/// <param name="results">a list of results for each content type in every minute.</param>
/// <param name="arrayLength">The plot length will the total number of minutes scanned, typically 1440 for one day.</param>
/// <param name="arrayStart">time start.</param>
public static Dictionary<string, double[]> ConvertResultsToDictionaryOfArrays(List<DescriptionResult> results, int arrayLength, int arrayStart)
{
var arrays = new Dictionary<string, double[]>();
Expand Down
12 changes: 2 additions & 10 deletions src/AudioAnalysisTools/ContentDescriptionTools/TemplateManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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; }

/// <summary>
/// Gets or sets a value indicating whether to use the template or not.
/// UseStatus can be true or false.
Expand Down

0 comments on commit c342e0b

Please sign in to comment.