public void TestChromelessImage() { var indexPropertiesFile = ConfigFile.Default <IndexPropertiesCollection>(); var indexProperties = ConfigFile.Deserialize <IndexPropertiesCollection>(indexPropertiesFile); var indexSpectrograms = new Dictionary <string, double[, ]>(6); var indexStatistics = new Dictionary <string, IndexDistributions.SpectralStats>(); var keys = (LDSpectrogramRGB.DefaultColorMap1 + "-" + LDSpectrogramRGB.DefaultColorMap2).Split('-'); foreach (var key in keys) { var matrix = new double[256, 60].Fill(indexProperties[key].DefaultValue); indexSpectrograms.Add(key, matrix); double[] array = DataTools.Matrix2Array(matrix); indexStatistics.Add(key, IndexDistributions.GetModeAndOneTailedStandardDeviation(array, 300, IndexDistributions.UpperPercentileDefault)); } var images = LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( inputDirectory: null, outputDirectory: this.outputDirectory, ldSpectrogramConfig: new LdSpectrogramConfig(), indexPropertiesConfigPath: indexPropertiesFile, indexGenerationData: new IndexGenerationData() { AnalysisStartOffset = 0.Seconds(), FrameLength = 512, IndexCalculationDuration = 60.0.Seconds(), RecordingBasename = "RGB_TEST", RecordingDuration = 60.0.Seconds(), SampleRateResampled = 22050, }, basename: "RGB_TEST", analysisType: AcousticIndices.AnalysisName, indexSpectrograms: indexSpectrograms, summaryIndices: Enumerable .Range(0, 60) .Select((x) => new SummaryIndexValues(60.0.Seconds(), indexProperties)) .Cast <SummaryIndexBase>() .ToArray(), indexStatistics: indexStatistics, imageChrome: ImageChrome.Without); foreach (var(image, key) in images) { Assert.That.ImageIsSize(60, 256, image); Assert.That.ImageRegionIsColor(Rectangle.FromLTRB(0, 0, 60, 256), Color.Black, (Bitmap)image); } }
/// <summary> /// ONLY Use this concatenation method when you want to concatenate the files for a fixed single day. /// The files to be concatenated must be somewhere in the subdirectory structure of the passed list of data directories /// Read them into a dictionary /// MOST RECENT METHOD TO CONCATENATE Spectral INDEX.CSV FILES - Early September 2015. /// It is designed to deal with Yvonne's case where want to concatenate files distributed over arbitrary directories. /// It only merges files for the passed fixed date. i.e only 24 hours /// </summary> public static void DrawSpectralIndexFiles( Dictionary <string, double[, ]> dictionary, LdSpectrogramConfig sgConfig, IndexGenerationData indexGenerationData, FileInfo indexPropertiesConfigFileInfo, DirectoryInfo opDir, SiteDescription siteDescription, FileInfo sunriseDataFile = null, List <GapsAndJoins> segmentErrors = null) { // derive new indices such as sqrt(PMN), NCDI etc -- main reason for this is to view what their distributions look like. dictionary = IndexMatrices.AddDerivedIndices(dictionary); // Calculate the index distribution statistics and write to a json file. Also save as png image if (indexGenerationData.RecordingStartDate != null) { DateTimeOffset dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; string dateString = $"{dto.Year}{dto.Month:D2}{dto.Day:D2}"; string opFileStem = $"{siteDescription.SiteName}_{dateString}"; var indexDistributions = IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionary, opDir, opFileStem); //SummaryIndexBase[] summaryIndices = null; string analysisType = "Towsey.Acoustic"; Tuple <Image, string>[] tuple = LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( opDir, // topLevelDirectories[0], // this should not be required but it is - because things have gotten complicated ! opDir, sgConfig, indexPropertiesConfigFileInfo, indexGenerationData, opFileStem, analysisType, dictionary, null, //summaryIndices, indexDistributions, siteDescription, sunriseDataFile, segmentErrors, ImageChrome.With); } }
public static void DrawSummaryIndexFiles( Dictionary <string, double[]> dictionaryOfCsvColumns, IndexGenerationData indexGenerationData, FileInfo indexPropertiesConfigFileInfo, DirectoryInfo opDir, SiteDescription siteDescription, FileInfo sunriseDatafile = null, List <GapsAndJoins> erroneousSegments = null, // info if have fatal errors i.e. no signal bool verbose = false) { var dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; string dateString = $"{dto.Year}{dto.Month:D2}{dto.Day:D2}"; string opFileStem = $"{siteDescription.SiteName}_{dateString}"; // Calculate the index distribution statistics and write to a json file. Also save as png image var indexDistributions = IndexDistributions.WriteSummaryIndexDistributionStatistics(dictionaryOfCsvColumns, opDir, opFileStem); var start = ((DateTimeOffset)indexGenerationData.RecordingStartDate).TimeOfDay; string startTime = $"{start.Hours:d2}{start.Minutes:d2}h"; if (start.Hours == 0 && start.Minutes == 0) { startTime = "midnight"; } string titletext = $"SOURCE: \"{opFileStem}\". Starts at {startTime} {Meta.OrganizationTag}"; Bitmap tracksImage = IndexDisplay.DrawImageOfSummaryIndices( IndexProperties.GetIndexProperties(indexPropertiesConfigFileInfo), dictionaryOfCsvColumns, titletext, indexGenerationData.IndexCalculationDuration, indexGenerationData.RecordingStartDate, sunriseDatafile, erroneousSegments, verbose); var imagePath = FilenameHelpers.AnalysisResultPath(opDir, opFileStem, SummaryIndicesStr, ImgFileExt); tracksImage.Save(imagePath); }
public void SummariseResults(AnalysisSettings settings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results) { var acousticIndicesConfig = (AcousticIndicesConfig)settings.AnalysisAnalyzerSpecificConfiguration; var sourceAudio = inputFileSegment.Source; var resultsDirectory = AnalysisCoordinator.GetNamedDirectory(settings.AnalysisOutputDirectory, this); bool tileOutput = acousticIndicesConfig.TileOutput; var frameWidth = acousticIndicesConfig.FrameLength; int sampleRate = AppConfigHelper.DefaultTargetSampleRate; sampleRate = acousticIndicesConfig.ResampleRate ?? sampleRate; // Gather settings for rendering false color spectrograms var ldSpectrogramConfig = acousticIndicesConfig.LdSpectrogramConfig; string basename = Path.GetFileNameWithoutExtension(sourceAudio.Name); // output to disk (so other analyzers can use the data, // only data - configuration settings that generated these indices // this data can then be used by post-process analyses /* NOTE: The value for FrameStep is used only when calculating a standard spectrogram * FrameStep is NOT used when calculating Summary and Spectral indices. */ var indexConfigData = new IndexGenerationData() { RecordingExtension = inputFileSegment.Source.Extension, RecordingBasename = basename, RecordingStartDate = inputFileSegment.TargetFileStartDate, RecordingDuration = inputFileSegment.TargetFileDuration.Value, SampleRateOriginal = inputFileSegment.TargetFileSampleRate.Value, SampleRateResampled = sampleRate, FrameLength = frameWidth, FrameStep = settings.Configuration.GetIntOrNull(AnalysisKeys.FrameStep) ?? frameWidth, IndexCalculationDuration = acousticIndicesConfig.IndexCalculationDurationTimeSpan, BgNoiseNeighbourhood = acousticIndicesConfig.BgNoiseBuffer, AnalysisStartOffset = inputFileSegment.SegmentStartOffset ?? TimeSpan.Zero, MaximumSegmentDuration = settings.AnalysisMaxSegmentDuration, BackgroundFilterCoeff = SpectrogramConstants.BACKGROUND_FILTER_COEFF, LongDurationSpectrogramConfig = ldSpectrogramConfig, }; var icdPath = FilenameHelpers.AnalysisResultPath( resultsDirectory, basename, IndexGenerationData.FileNameFragment, "json"); 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 var indexDistributions = IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionaryOfSpectra, resultsDirectory, basename); // HACK: do not render false color spectrograms unless IndexCalculationDuration = 60.0 (the normal resolution) if (acousticIndicesConfig.IndexCalculationDurationTimeSpan != 60.0.Seconds()) { Log.Warn("False color spectrograms were not rendered"); } else { FileInfo indicesPropertiesConfig = acousticIndicesConfig.IndexPropertiesConfig.ToFileInfo(); // Actually draw false color / long duration spectrograms Tuple <Image <Rgb24>, string>[] images = LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( inputDirectory: resultsDirectory, outputDirectory: resultsDirectory, ldSpectrogramConfig: ldSpectrogramConfig, indexPropertiesConfigPath: indicesPropertiesConfig, indexGenerationData: indexConfigData, basename: basename, analysisType: this.Identifier, indexSpectrograms: dictionaryOfSpectra, indexStatistics: indexDistributions, imageChrome: (!tileOutput).ToImageChrome()); if (tileOutput) { Debug.Assert(images.Length == 2); Log.Info("Tiling output at scale: " + acousticIndicesConfig.IndexCalculationDuration); foreach (var image in images) { TileOutput(resultsDirectory, Path.GetFileNameWithoutExtension(sourceAudio.Name), image.Item2 + ".Tile", inputFileSegment, image.Item1); } } } }
public static void Execute(Arguments arguments) { if (arguments == null) { throw new NoDeveloperMethodException(); } string date = "# DATE AND TIME: " + DateTime.Now; LoggedConsole.WriteLine("# DRAW LONG DURATION SPECTROGRAMS DERIVED FROM CSV FILES OF SPECTRAL INDICES OBTAINED FROM AN AUDIO RECORDING"); LoggedConsole.WriteLine(date); LoggedConsole.WriteLine("# Spectrogram Config file: " + arguments.FalseColourSpectrogramConfig); LoggedConsole.WriteLine("# Index Properties Config file: " + arguments.IndexPropertiesConfig); LoggedConsole.WriteLine(); (FileInfo indexGenerationDataFile, FileInfo indexDistributionsFile) = ZoomParameters.CheckNeededFilesExist(arguments.InputDataDirectory.ToDirectoryInfo()); var indexGenerationData = Json.Deserialize <IndexGenerationData>(indexGenerationDataFile); // spectral distribution statistics is required only when calcualting difference spectrograms. Dictionary <string, IndexDistributions.SpectralStats> indexDistributionsData = null; if (indexDistributionsFile != null && indexDistributionsFile.Exists) { indexDistributionsData = IndexDistributions.Deserialize(indexDistributionsFile); } // this config can be found in IndexGenerationData. If config argument not specified, simply take it from icd file LdSpectrogramConfig config; if (arguments.FalseColourSpectrogramConfig == null) { config = indexGenerationData.LongDurationSpectrogramConfig; } else { config = LdSpectrogramConfig.ReadYamlToConfig(arguments.FalseColourSpectrogramConfig.ToFileInfo()); } FilenameHelpers.ParseAnalysisFileName(indexGenerationDataFile, out var originalBaseName, out var _, out var _); // CHECK FOR ERROR SEGMENTS - get zero signal array var input = arguments.InputDataDirectory.ToDirectoryInfo(); var csvFile = new FileInfo(Path.Combine(input.FullName, originalBaseName + "__Towsey.Acoustic.Indices.csv")); //Dictionary<string, double[]> summaryIndices = CsvTools.ReadCSVFile2Dictionary(csvFile.FullName); //var summaryIndices = Csv.ReadFromCsv<Dictionary<string, double[]>>(csvFile); var summaryIndices = Csv.ReadFromCsv <SummaryIndexValues>(csvFile); var indexErrors = GapsAndJoins.DataIntegrityCheckForZeroSignal(summaryIndices); //config.IndexCalculationDuration = TimeSpan.FromSeconds(1.0); //config.XAxisTicInterval = TimeSpan.FromSeconds(60.0); //config.IndexCalculationDuration = TimeSpan.FromSeconds(60.0); //config.XAxisTicInterval = TimeSpan.FromSeconds(3600.0); LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( inputDirectory: input, outputDirectory: arguments.OutputDirectory.ToDirectoryInfo(), ldSpectrogramConfig: config, indexPropertiesConfigPath: arguments.IndexPropertiesConfig.ToFileInfo(), indexGenerationData: indexGenerationData, basename: originalBaseName, analysisType: AcousticIndices.TowseyAcoustic, indexSpectrograms: null, indexStatistics: indexDistributionsData, segmentErrors: indexErrors, imageChrome: false.ToImageChrome()); Log.Success("Draw Long Duration Spectrograms complete!"); }
public static Image <Rgb24> DrawDistanceSpectrogram(LDSpectrogramRGB cs1, LDSpectrogramRGB cs2) { string[] keys = cs1.ColorMap.Split('-'); string key = keys[0]; double[,] m1Red = cs1.GetNormalisedSpectrogramMatrix(key); IndexDistributions.SpectralStats stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Red); cs1.IndexStats.Add(key, stats); m1Red = MatrixTools.Matrix2ZScores(m1Red, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("1.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); key = keys[1]; double[,] m1Grn = cs1.GetNormalisedSpectrogramMatrix(key); stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Grn); cs1.IndexStats.Add(key, stats); m1Grn = MatrixTools.Matrix2ZScores(m1Grn, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("1.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); key = keys[2]; double[,] m1Blu = cs1.GetNormalisedSpectrogramMatrix(key); stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m1Blu); cs1.IndexStats.Add(key, stats); m1Blu = MatrixTools.Matrix2ZScores(m1Blu, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("1.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); key = keys[0]; double[,] m2Red = cs2.GetNormalisedSpectrogramMatrix(key); stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Red); cs2.IndexStats.Add(key, stats); m2Red = MatrixTools.Matrix2ZScores(m2Red, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("2.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); key = keys[1]; double[,] m2Grn = cs2.GetNormalisedSpectrogramMatrix(key); stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Grn); cs2.IndexStats.Add(key, stats); m2Grn = MatrixTools.Matrix2ZScores(m2Grn, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("2.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); key = keys[2]; double[,] m2Blu = cs2.GetNormalisedSpectrogramMatrix(key); stats = IndexDistributions.GetModeAndOneTailedStandardDeviation(m2Blu); cs2.IndexStats.Add(key, stats); m2Blu = MatrixTools.Matrix2ZScores(m2Blu, stats.Mode, stats.StandardDeviation); ////LoggedConsole.WriteLine("2.{0}: Min={1:f2} Max={2:f2} Mode={3:f2}+/-{4:f3} (SD=One-tailed)", key, dict["min"], dict["max"], dict["mode"], dict["sd"]); var v1 = new double[3]; double[] mode1 = { cs1.IndexStats[keys[0]].Mode, cs1.IndexStats[keys[1]].Mode, cs1.IndexStats[keys[2]].Mode, }; double[] stDv1 = { cs1.IndexStats[keys[0]].StandardDeviation, cs1.IndexStats[keys[1]].StandardDeviation, cs1.IndexStats[keys[2]].StandardDeviation, }; LoggedConsole.WriteLine( "1: avACI={0:f3}+/-{1:f3}; avTEN={2:f3}+/-{3:f3}; avCVR={4:f3}+/-{5:f3}", mode1[0], stDv1[0], mode1[1], stDv1[1], mode1[2], stDv1[2]); var v2 = new double[3]; double[] mode2 = { cs2.IndexStats[keys[0]].Mode, cs2.IndexStats[keys[1]].Mode, cs2.IndexStats[keys[2]].Mode, }; double[] stDv2 = { cs2.IndexStats[keys[0]].StandardDeviation, cs2.IndexStats[keys[1]].StandardDeviation, cs2.IndexStats[keys[2]].StandardDeviation, }; LoggedConsole.WriteLine( "2: avACI={0:f3}+/-{1:f3}; avTEN={2:f3}+/-{3:f3}; avCVR={4:f3}+/-{5:f3}", mode2[0], stDv2[0], mode2[1], stDv2[1], mode2[2], stDv2[2]); // assume all matrices are normalised and of the same dimensions int rows = m1Red.GetLength(0); // number of rows int cols = m1Red.GetLength(1); // number var d12Matrix = new double[rows, cols]; var d11Matrix = new double[rows, cols]; var d22Matrix = new double[rows, cols]; for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { v1[0] = m1Red[row, col]; v1[1] = m1Grn[row, col]; v1[2] = m1Blu[row, col]; v2[0] = m2Red[row, col]; v2[1] = m2Grn[row, col]; v2[2] = m2Blu[row, col]; d12Matrix[row, col] = DataTools.EuclideanDistance(v1, v2); d11Matrix[row, col] = (v1[0] + v1[1] + v1[2]) / 3; // get average of the normalised values d22Matrix[row, col] = (v2[0] + v2[1] + v2[2]) / 3; // following lines are for debugging purposes // if ((row == 150) && (col == 1100)) // { // LoggedConsole.WriteLine("V1={0:f3}, {1:f3}, {2:f3}", v1[0], v1[1], v1[2]); // LoggedConsole.WriteLine("V2={0:f3}, {1:f3}, {2:f3}", v2[0], v2[1], v2[2]); // LoggedConsole.WriteLine("EDist12={0:f4}; ED11={1:f4}; ED22={2:f4}", d12Matrix[row, col], d11Matrix[row, col], d22Matrix[row, col]); // } } } double[] array = DataTools.Matrix2Array(d12Matrix); NormalDist.AverageAndSD(array, out var avDist, out var sdDist); for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { d12Matrix[row, col] = (d12Matrix[row, col] - avDist) / sdDist; } } double zScore; Dictionary <string, Color> colourChart = GetDifferenceColourChart(); Color colour; var bmp = new Image <Rgb24>(cols, rows); for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { zScore = d12Matrix[row, col]; if (d11Matrix[row, col] >= d22Matrix[row, col]) { if (zScore > 3.08) { colour = colourChart["+99.9%"]; } // 99.9% conf else { if (zScore > 2.33) { colour = colourChart["+99.0%"]; } // 99.0% conf else { if (zScore > 1.65) { colour = colourChart["+95.0%"]; } // 95% conf else { if (zScore < 0.0) { colour = colourChart["NoValue"]; } else { // v = Convert.ToInt32(zScore * MaxRGBValue); // colour = Color.FromRgb(v, 0, v); colour = colourChart["+NotSig"]; } } } } // if() else bmp[col, row] = colour; } else { if (zScore > 3.08) { colour = colourChart["-99.9%"]; } // 99.9% conf else { if (zScore > 2.33) { colour = colourChart["-99.0%"]; } // 99.0% conf else { if (zScore > 1.65) { colour = colourChart["-95.0%"]; } // 95% conf else { if (zScore < 0.0) { colour = colourChart["NoValue"]; } else { // v = Convert.ToInt32(zScore * MaxRGBValue); // if() // colour = Color.FromRgb(0, v, v); colour = colourChart["-NotSig"]; } } } } // if() else bmp[col, row] = colour; } } // all rows } // all rows return(bmp); }
public override void SummariseResults( AnalysisSettings analysisSettings, FileSegment inputFileSegment, EventBase[] events, SummaryIndexBase[] indices, SpectralIndexBase[] spectralIndices, AnalysisResult2[] results) { // below is example of how to access values in ContentDescription config file. //sampleRate = analysisSettings.Configuration.GetIntOrNull(AnalysisKeys.ResampleRate) ?? sampleRate; var cdConfiguration = (CdConfig)analysisSettings.Configuration; var ldSpectrogramConfig = cdConfiguration.LdSpectrogramConfig; //var cdConfigFile = analysisSettings.ConfigFile; //var configDirectory = cdConfigFile.DirectoryName ?? throw new ArgumentNullException(nameof(cdConfigFile), "Null value"); var sourceAudio = inputFileSegment.Source; string basename = Path.GetFileNameWithoutExtension(sourceAudio.Name); var resultsDirectory = AnalysisCoordinator.GetNamedDirectory(analysisSettings.AnalysisOutputDirectory, this); // check for null values - this was recommended by ReSharper! if (inputFileSegment.TargetFileDuration == null || inputFileSegment.TargetFileSampleRate == null) { throw new NullReferenceException(); } // 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. var indexConfigData = new IndexGenerationData() { RecordingExtension = inputFileSegment.Source.Extension, RecordingBasename = basename, RecordingStartDate = inputFileSegment.TargetFileStartDate, RecordingDuration = inputFileSegment.TargetFileDuration.Value, SampleRateOriginal = inputFileSegment.TargetFileSampleRate.Value, SampleRateResampled = ContentSignatures.SampleRate, FrameLength = ContentSignatures.FrameSize, FrameStep = ContentSignatures.FrameSize, IndexCalculationDuration = TimeSpan.FromSeconds(ContentSignatures.IndexCalculationDurationInSeconds), BgNoiseNeighbourhood = TimeSpan.FromSeconds(5), // default value for content description AnalysisStartOffset = inputFileSegment.SegmentStartOffset ?? TimeSpan.Zero, MaximumSegmentDuration = analysisSettings.AnalysisMaxSegmentDuration, BackgroundFilterCoeff = SpectrogramConstants.BACKGROUND_FILTER_COEFF, LongDurationSpectrogramConfig = ldSpectrogramConfig, }; var icdPath = FilenameHelpers.AnalysisResultPath( resultsDirectory, basename, IndexGenerationData.FileNameFragment, "json"); Json.Serialise(icdPath.ToFileInfo(), indexConfigData); // gather spectra to form spectrograms. Assume same spectra in all analyzer results var dictionaryOfSpectra = spectralIndices.ToTwoDimensionalArray(SpectralIndexValuesForContentDescription.CachedSelectors, TwoDimensionalArray.Rotate90ClockWise); // Calculate the index distribution statistics and write to a json file. Also save as png image // The following method returns var indexDistributions =, but we have no use for them. IndexDistributions.WriteSpectralIndexDistributionStatistics(dictionaryOfSpectra, resultsDirectory, basename); // Draw ldfc spectrograms and return path to 2maps image. string ldfcSpectrogramPath = DrawSpectrogramsFromSpectralIndices( ldSpectrogramConfig, outputDirectory: resultsDirectory, indexGenerationData: indexConfigData, basename: basename, indexSpectrograms: dictionaryOfSpectra); // 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"); // 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); var images = GraphsAndCharts.DrawPlotDistributions(contentPlots); var plotsImage = ImageTools.CombineImagesVertically(images); plotsImage.Save(Path.Combine(resultsDirectory.FullName, "DistributionsOfContentScores.png")); // Attach content description plots to LDFC spectrogram and write to file var ldfcSpectrogram = Image.Load <Rgb24>(ldfcSpectrogramPath); var image = ContentVisualization.DrawLdfcSpectrogramWithContentScoreTracks(ldfcSpectrogram, contentPlots); var path3 = Path.Combine(resultsDirectory.FullName, basename + ".ContentDescription.png"); image.Save(path3); }