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 void DrawStackOfZoomedSpectrograms( DirectoryInfo inputDirectory, DirectoryInfo outputDirectory, AnalysisIoInputDirectory io, ZoomParameters common, string analysisTag, TimeSpan focalTime, int imageWidth) { var zoomConfig = common.SpectrogramZoomingConfig; LdSpectrogramConfig ldsConfig = common.SpectrogramZoomingConfig.LdSpectrogramConfig; //var distributions = common.IndexDistributions; string fileStem = common.OriginalBasename; var indexGeneration = common.IndexGenerationData; TimeSpan dataScale = indexGeneration.IndexCalculationDuration; // ####################### DERIVE ZOOMED OUT SPECTROGRAMS FROM SPECTRAL INDICES //var indexGenerationData = common.IndexGenerationData; var indexProperties = zoomConfig.IndexProperties; var(spectra, filteredIndexProperties) = ZoomCommon.LoadSpectra(io, analysisTag, fileStem, zoomConfig.LdSpectrogramConfig, indexProperties); Stopwatch sw = Stopwatch.StartNew(); // Set the default time-scales in seconds per pixel. // These were changed on 3rd April 2019 to better match those in the current zooming config file. double[] imageScales = { 60, 30, 15, 7.5, 3.2, 1.6, 0.8, 0.4, 0.2 }; if (zoomConfig.SpectralIndexScale != null) { imageScales = zoomConfig.SpectralIndexScale; } sw = Stopwatch.StartNew(); int scaleCount = imageScales.Length; var imageList = new List <Image <Rgb24> >(); for (int i = 0; i < scaleCount; i++) { var imageScale = TimeSpan.FromSeconds(imageScales[i]); var image = DrawIndexSpectrogramAtScale(ldsConfig, indexGeneration, filteredIndexProperties, focalTime, dataScale, imageScale, imageWidth, spectra, fileStem); if (image != null) { imageList.Add(image); string name = $"{fileStem}_FocalZoom_min{focalTime.TotalMinutes:f1}_scale{imageScales[i]}.png"; image.Save(Path.Combine(outputDirectory.FullName, name)); } } sw.Stop(); LoggedConsole.WriteLine("Finished spectrograms derived from spectral indices. Elapsed time = " + sw.Elapsed.TotalSeconds + " seconds"); // NOTE: The following code is deprecated. It was originally developed to provide some intermediate steps between the hi-resolution false-colour spectrograms // and the standard grey scale spectrograms. // ####################### DERIVE ZOOMED IN SPECTROGRAMS FROM STANDARD SPECTRAL FRAMES /* * int[] compressionFactor = { 8, 4, 2, 1 }; * int compressionCount = compressionFactor.Length; * sw = Stopwatch.StartNew(); * double frameStepInSeconds = indexGeneration.FrameStep / (double)indexGeneration.SampleRateResampled; * TimeSpan frameScale = TimeSpan.FromTicks((long)Math.Round(frameStepInSeconds * 10000000)); * if (zoomConfig.SpectralFrameScale != null) * { * imageScales = zoomConfig.SpectralFrameScale; * * // TODO: CONVERT IMAGE scales into Compression factors. * compressionCount = imageScales.Length; * compressionFactor = new int[compressionCount]; * compressionFactor[compressionCount - 1] = 1; * double denom = imageScales[compressionCount - 1]; * * for (int i = 0; i < compressionCount - 1; i++) * { * compressionFactor[i] = (int)Math.Round(imageScales[i] / denom); * } * } * * int maxCompression = compressionFactor[0]; * TimeSpan maxImageDuration = TimeSpan.FromTicks(maxCompression * imageWidth * frameScale.Ticks); * * TimeSpan halfMaxImageDuration = TimeSpan.FromMilliseconds(maxImageDuration.TotalMilliseconds / 2); * TimeSpan startTimeOfMaxImage = TimeSpan.Zero; * if (focalTime != TimeSpan.Zero) * { * startTimeOfMaxImage = focalTime - halfMaxImageDuration; * } * * TimeSpan startTimeOfData = TimeSpan.FromMinutes(Math.Floor(startTimeOfMaxImage.TotalMinutes)); * * List<double[]> frameData = ReadFrameData(inputDirectory, fileStem, startTimeOfMaxImage, maxImageDuration, zoomConfig, indexGeneration.MaximumSegmentDuration.Value); * * // get the index data to add into the * // TimeSpan imageScale1 = TimeSpan.FromSeconds(0.1); * double[,] indexData = spectra["PMN"]; * * // make the images * for (int i = 0; i < compressionCount; i++) * { * int factor = compressionFactor[i]; * var image = DrawFrameSpectrogramAtScale(ldsConfig, indexGeneration, startTimeOfData, factor, frameData, indexData, focalTime, frameScale, imageWidth); * if (image != null) * { * imageList.Add(image); * } * } * * sw.Stop(); * LoggedConsole.WriteLine("Finished spectrograms derived from standard frames. Elapsed time = " + sw.Elapsed.TotalSeconds + " seconds"); */ // combine the images into a stack var combinedImage = ImageTools.CombineImagesVertically(imageList); string fileName = $"{fileStem}_FocalZOOM_min{focalTime.TotalMinutes:f1}.png"; combinedImage.Save(Path.Combine(outputDirectory.FullName, fileName)); }
/// <summary> /// HERVE GLOTIN /// Combined audio2csv + zooming spectrogram task. /// This is used to analyse Herve Glotin's BIRD50 data set. /// ############################# IMPORTANT ######################################## /// In order to analyse the short recordings in BIRD50 dataset, need following change to code: /// need to modify AudioAnalysis.AnalysisPrograms.AcousticIndices.cs #line648 /// need to change AnalysisMinSegmentDuration = TimeSpan.FromSeconds(20), /// to AnalysisMinSegmentDuration = TimeSpan.FromSeconds(1), /// THIS iS to analyse BIRD50 short recordings. /// </summary> public static void HiRes1() { string recordingPath = @"C:\SensorNetworks\WavFiles\TestRecordings\TEST_7min_artificial.wav"; //// HERVE GLOTIN BIRD50 TRAINING RECORDINGS //DirectoryInfo dataDir = new DirectoryInfo(@"D:\SensorNetworks\WavFiles\Glotin\Bird50\AmazonBird50_training_input"); //string parentDir = @"C:\SensorNetworks\Output\BIRD50"; //string speciesLabelsFile = parentDir + @"\AmazonBird50_training_output.csv"; //int speciesCount = 50; //////set file name format -depends on train or test. E.g. "ID0003"; //string fileStemFormatString = "ID{0:d4}"; // for training files //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml"; //string learningMode = "Train"; //// HERVE GLOTIN BIRD50 TESTING RECORDINGS DirectoryInfo dataDir = new DirectoryInfo(@"D:\SensorNetworks\WavFiles\Glotin\Bird50\AmazonBird50_testing_input"); string parentDir = @"C:\SensorNetworks\Output\BIRD50"; string speciesLabelsFile = null; int speciesCount = 50; ////set file name format -depends on train or test. E.g. "ID0003"; string fileStemFormatString = "ID1{0:d3}"; // for testing files string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml"; string learningMode = "Test"; // HERVE GLOTIN BOMBYX WHALE RECORDINGS //DirectoryInfo dataDir = new DirectoryInfo(@"C:\SensorNetworks\WavFiles\WhaleFromGlotin"); //string parentDir = @"C:\SensorNetworks\Output\Glotin\Bombyx_SpermWhales"; //string speciesLabelsFile = null; //int speciesCount = 0; //////set file name format -depends on train or test. E.g. "ID0003"; //string fileStemFormatString = null; ////string fileStemFormatString = "ID1{0:d3}"; // for testing files //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiResGianniPavan.yml"; //string learningMode = "Train"; // GIANNI PAVAN SASSAFRAS RECORDINGS //DirectoryInfo dataDir = new DirectoryInfo(@"C:\SensorNetworks\WavFiles\GianniPavan\SABIOD - TEST SASSOFRATINO"); //string parentDir = @"C:\SensorNetworks\Output\GianniPavan"; //string speciesLabelsFile = null; //int speciesCount = 0; //string fileStemFormatString = null; //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiResGianniPavan.yml"; //string learningMode = "Train"; // ###################################################################### string outputDir = parentDir + @"\" + learningMode; string imageOutputDir = parentDir + @"\" + learningMode + "Images"; string csvDir = outputDir + @"\Towsey.Acoustic"; string zoomOutputDir = outputDir; string audio2csvConfigPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\Towsey.AcousticHiRes.yml"; string hiResZoomConfigPath = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\SpectrogramHiResConfig.yml"; FileInfo[] wavFiles = { new FileInfo(recordingPath) }; // comment next two lines when debugging a single recording file string match = @"*.wav"; wavFiles = dataDir.GetFiles(match, SearchOption.AllDirectories); // READ IN THE SPECIES LABELS FILE AND SET UP THE DATA string[] fileID = new string[wavFiles.Length]; int[] speciesID = new int[speciesCount]; if (speciesLabelsFile != null) { BirdClefExperiment1.ReadGlotinsSpeciesLabelFile(speciesLabelsFile, wavFiles.Length, out fileID, out speciesID); } else // make seperate species name for each file { speciesID = new int[wavFiles.Length]; } //LOOP THROUGH ALL WAV FILES //for (int i = 538; i < 539; i++) //for (int i = 0; i < 8; i++) for (int i = 0; i < wavFiles.Length; i++) { FileInfo file = wavFiles[i]; recordingPath = file.FullName; string idName = Path.GetFileNameWithoutExtension(file.FullName); string name = string.Format("{0}_Species{1:d2}", idName, speciesID[i]); outputDir = parentDir + @"\" + learningMode + @"\" + name; csvDir = parentDir + @"\" + learningMode + @"\" + name + @"\Towsey.Acoustic"; zoomOutputDir = outputDir; Console.WriteLine("\n\n"); Console.WriteLine($@">>>>{i}: File<{name}>"); try { // A: analyse the recording files == audio2csv. var audio2csvArguments = new AnalyseLongRecordings.AnalyseLongRecording.Arguments { Source = recordingPath.ToFileInfo(), Config = audio2csvConfigPath, Output = outputDir.ToDirectoryInfo(), }; if (!audio2csvArguments.Source.Exists) { LoggedConsole.WriteWarnLine(" >>>>>>>>>>>> WARNING! The Source Recording file cannot be found! This will cause an exception."); } if (!File.Exists(audio2csvArguments.Config)) { LoggedConsole.WriteWarnLine(" >>>>>>>>>>>> WARNING! The Configuration file cannot be found! This will cause an exception."); } AnalyseLongRecordings.AnalyseLongRecording.Execute(audio2csvArguments); // B: Concatenate the summary indices and produce images // Use the Zoomingspectrograms action. // need to find out how long the recording is. string fileName = audio2csvArguments.Source.BaseName(); string testFileName = fileName + @"__Towsey.Acoustic.ACI.csv"; List <string> data = FileTools.ReadTextFile(Path.Combine(csvDir, testFileName)); int lineCount = data.Count - 1; // -1 for header. int imageWidth = lineCount; //assume scale is index calculation duration = 0.1s // i.e. image resolution 0.1s/px. or 600px/min double focalMinute = (double)lineCount / 600 / 2; if (focalMinute < 0.016666) { focalMinute = 0.016666; // shortest recording = 1 second. } var zoomingArguments = new DrawZoomingSpectrograms.Arguments { // use the default set of index properties in the AnalysisConfig directory. SourceDirectory = csvDir, Output = zoomOutputDir, SpectrogramZoomingConfig = hiResZoomConfigPath, // draw a focused multi-resolution pyramid of images ZoomAction = DrawZoomingSpectrograms.Arguments.ZoomActionType.Focused, }; LoggedConsole.WriteLine("# Spectrogram Zooming config : " + zoomingArguments.SpectrogramZoomingConfig); LoggedConsole.WriteLine("# Input Directory : " + zoomingArguments.SourceDirectory); LoggedConsole.WriteLine("# Output Directory : " + zoomingArguments.Output); var common = new ZoomParameters(zoomingArguments.SourceDirectory.ToDirectoryEntry(), zoomingArguments.SpectrogramZoomingConfig.ToFileEntry(), false); // Create directory if not exists if (!Directory.Exists(zoomingArguments.Output)) { Directory.CreateDirectory(zoomingArguments.Output); } ZoomFocusedSpectrograms.DrawStackOfZoomedSpectrograms( zoomingArguments.SourceDirectory.ToDirectoryInfo(), zoomingArguments.Output.ToDirectoryInfo(), common, TimeSpan.FromMinutes(focalMinute), imageWidth, AcousticIndices.TowseyAcoustic); // DRAW THE VARIOUS IMAGES string fileStem = fileName; if (fileStemFormatString != null) { fileStem = string.Format(fileStemFormatString, i + 1); // training images } var ldfcSpectrogramArguments = new DrawLongDurationSpectrograms.Arguments { // use the default set of index properties in the AnalysisConfig directory. InputDataDirectory = csvDir, OutputDirectory = imageOutputDir, IndexPropertiesConfig = indexPropertiesConfig, }; // there are two possible tasks // 1: draw the aggregated grey scale spectrograms int secDuration = DrawLongDurationSpectrograms.DrawAggregatedSpectrograms(ldfcSpectrogramArguments, fileStem); // 2: draw the coloured ridge spectrograms secDuration = DrawLongDurationSpectrograms.DrawRidgeSpectrograms(ldfcSpectrogramArguments, fileStem); // copy files // POW, EVN, SPT, RHZ, RVT, RPS, RNG string[] copyArray = { "POW", "EVN", "SPT", "RHZ", "RVT", "RPS", "RNG" }; DirectoryInfo sourceDirectory = new DirectoryInfo(csvDir); string destinationDirectory = parentDir + @"\TrainingClassifier"; foreach (string key in copyArray) { // ID0002__Towsey.Acoustic.BGN.csv fileName += @"__Towsey.Acoustic.ACI.csv"; string sourceFileName = string.Format(idName + "__Towsey.Acoustic." + key + ".csv"); string sourcePath = Path.Combine(sourceDirectory.FullName, sourceFileName); string nameOfParentDirectory = sourceDirectory.Parent.Name; string destinationFileName = string.Format(nameOfParentDirectory + "." + key + ".csv"); string destinationPath = Path.Combine(destinationDirectory, destinationFileName); File.Copy(sourcePath, destinationPath, true); } } // try block catch (Exception e) { LoggedConsole.WriteErrorLine(string.Format("ERROR!!!!! RECORDING {0} FILE {1}", i, name)); LoggedConsole.WriteErrorLine(string.Format(e.ToString())); } } // end loop through all wav files } // HiRes1()
public static void Execute(Arguments arguments) { if (arguments == null) { throw new NoDeveloperMethodException(); } string description; switch (arguments.ZoomAction) { case Arguments.ZoomActionType.Focused: description = "# DRAW STACK OF FOCUSED MULTI-SCALE LONG DURATION SPECTROGRAMS DERIVED FROM SPECTRAL INDICES."; break; case Arguments.ZoomActionType.Tile: description = "# DRAW ZOOMING SPECTROGRAMS DERIVED FROM SPECTRAL INDICES OBTAINED FROM AN AUDIO RECORDING"; break; default: throw new ArgumentOutOfRangeException(); } LoggedConsole.WriteLine(description); LoggedConsole.WriteLine("# Spectrogram Zooming config : " + arguments.SpectrogramZoomingConfig); LoggedConsole.WriteLine("# Input Directory : " + arguments.SourceDirectory); LoggedConsole.WriteLine("# Output Directory : " + arguments.Output); var common = new ZoomParameters( arguments.SourceDirectory.ToDirectoryEntry(), arguments.SpectrogramZoomingConfig.ToFileEntry(), !string.IsNullOrEmpty(arguments.OutputFormat)); LoggedConsole.WriteLine("# File name of recording : " + common.OriginalBasename); // create file systems for reading input and writing output var io = FileSystemProvider.GetInputOutputFileSystems( arguments.SourceDirectory, FileSystemProvider.MakePath(arguments.Output, common.OriginalBasename, arguments.OutputFormat, "Tiles")) .EnsureInputIsDirectory(); switch (arguments.ZoomAction) { case Arguments.ZoomActionType.Focused: // draw a focused multi-resolution pyramid of images TimeSpan focalTime; if (arguments.FocusMinute.HasValue) { focalTime = TimeSpan.FromMinutes(arguments.FocusMinute.Value); } else { throw new ArgumentException("FocusMinute is null, cannot proceed"); } const int ImageWidth = 1500; ZoomFocusedSpectrograms.DrawStackOfZoomedSpectrograms( arguments.SourceDirectory.ToDirectoryInfo(), arguments.Output.ToDirectoryInfo(), common, focalTime, ImageWidth, AcousticIndices.TowseyAcoustic); break; case Arguments.ZoomActionType.Tile: // Create the super tiles for a full set of recordings ZoomTiledSpectrograms.DrawTiles( io, common, AcousticIndices.TowseyAcoustic); break; default: Log.Warn("Other ZoomAction results in standard LD Spectrogram to be drawn"); // draw standard false color spectrograms - useful to check what spectrograms of the individual // indices are like. throw new NotImplementedException(); /*LDSpectrogramRGB.DrawSpectrogramsFromSpectralIndices( * arguments.SourceDirectory, * arguments.Output, * arguments.SpectrogramConfigPath, * arguments.IndexPropertiesConfig);*/ break; } }
public static void DrawStackOfZoomedSpectrograms(DirectoryInfo inputDirectory, DirectoryInfo outputDirectory, ZoomParameters common, TimeSpan focalTime, int imageWidth, string analysisType) { var zoomConfig = common.SpectrogramZoomingConfig; LdSpectrogramConfig ldsConfig = common.SpectrogramZoomingConfig.LdSpectrogramConfig; var distributions = common.IndexDistributions; var indexGeneration = common.IndexGenerationData; string fileStem = common.OriginalBasename; TimeSpan dataScale = indexGeneration.IndexCalculationDuration; // ####################### DERIVE ZOOMED OUT SPECTROGRAMS FROM SPECTRAL INDICES string[] keys = { "ACI", "BGN", "CVR", "DIF", "ENT", "EVN", "PMN", "POW", "RHZ", "RVT", "RPS", "RNG", "SUM", "SPT" }; var indexProperties = InitialiseIndexProperties.FilterIndexPropertiesForSpectralOnly(zoomConfig.IndexProperties); Dictionary <string, double[, ]> spectra = IndexMatrices.ReadSpectralIndices(inputDirectory, fileStem, analysisType, keys); Stopwatch sw = Stopwatch.StartNew(); // standard scales in seconds per pixel. double[] imageScales = { 60, 24, 12, 6, 2, 1, 0.6, 0.2 }; if (zoomConfig.SpectralIndexScale != null) { imageScales = zoomConfig.SpectralIndexScale; } sw = Stopwatch.StartNew(); int scaleCount = imageScales.Length; var imageList = new List <Image>(); for (int i = 0; i < scaleCount; i++) { var imageScale = TimeSpan.FromSeconds(imageScales[i]); var image = DrawIndexSpectrogramAtScale(ldsConfig, indexGeneration, indexProperties, focalTime, dataScale, imageScale, imageWidth, spectra, fileStem); if (image != null) { imageList.Add(image); string name = $"{fileStem}_FocalZoom_min{focalTime.TotalMinutes:f1}_scale{imageScales[i]}.png"; image.Save(Path.Combine(outputDirectory.FullName, name)); } } sw.Stop(); LoggedConsole.WriteLine("Finished spectrograms derived from spectral indices. Elapsed time = " + sw.Elapsed.TotalSeconds + " seconds"); // ####################### DERIVE ZOOMED IN SPECTROGRAMS FROM STANDARD SPECTRAL FRAMES int[] compressionFactor = { 8, 4, 2, 1 }; int compressionCount = compressionFactor.Length; sw = Stopwatch.StartNew(); double frameStepInSeconds = indexGeneration.FrameStep / (double)indexGeneration.SampleRateResampled; TimeSpan frameScale = TimeSpan.FromTicks((long)Math.Round(frameStepInSeconds * 10000000)); if (zoomConfig.SpectralFrameScale != null) { imageScales = zoomConfig.SpectralFrameScale; // TODO: CONVERT IMAGE scales into Compression factors. compressionCount = imageScales.Length; compressionFactor = new int[compressionCount]; compressionFactor[compressionCount - 1] = 1; double denom = imageScales[compressionCount - 1]; for (int i = 0; i < compressionCount - 1; i++) { compressionFactor[i] = (int)Math.Round(imageScales[i] / denom); } } int maxCompression = compressionFactor[0]; TimeSpan maxImageDuration = TimeSpan.FromTicks(maxCompression * imageWidth * frameScale.Ticks); TimeSpan halfMaxImageDuration = TimeSpan.FromMilliseconds(maxImageDuration.TotalMilliseconds / 2); TimeSpan startTimeOfMaxImage = TimeSpan.Zero; if (focalTime != TimeSpan.Zero) { startTimeOfMaxImage = focalTime - halfMaxImageDuration; } TimeSpan startTimeOfData = TimeSpan.FromMinutes(Math.Floor(startTimeOfMaxImage.TotalMinutes)); List <double[]> frameData = ReadFrameData(inputDirectory, fileStem, startTimeOfMaxImage, maxImageDuration, zoomConfig, indexGeneration.MaximumSegmentDuration.Value); // get the index data to add into the // TimeSpan imageScale1 = TimeSpan.FromSeconds(0.1); double[,] indexData = spectra["POW"]; // make the images for (int i = 0; i < compressionCount; i++) { int factor = compressionFactor[i]; var image = DrawFrameSpectrogramAtScale(ldsConfig, indexGeneration, startTimeOfData, factor, frameData, indexData, focalTime, frameScale, imageWidth); if (image != null) { imageList.Add(image); } } sw.Stop(); LoggedConsole.WriteLine("Finished spectrograms derived from standard frames. Elapsed time = " + sw.Elapsed.TotalSeconds + " seconds"); // combine the images into a stack Image combinedImage = ImageTools.CombineImagesVertically(imageList); string fileName = $"{fileStem}_FocalZOOM_min{focalTime.TotalMinutes:f1}.png"; combinedImage.Save(Path.Combine(outputDirectory.FullName, fileName)); }