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)); }
[DataRow(0.1, 35990)] // at this scale 10 pixels are missing... image is padded by 0 px, and misisng 10px public void TestImagesHaveCorrectLength(double renderScale, int expectedWidth) { // this value just represents a 'render as much as you can until this limit' threshold const int ImageWidth = 1024; var dataScale = 0.1.Seconds(); var imageScale = renderScale.Seconds(); var recordingDuration = 3599.Seconds(); // we simulate rendering the last tile at each resolution var tileDuration = imageScale.Multiply(ImageWidth); var numberOfTiles = (int)Math.Floor(recordingDuration.TotalSeconds / tileDuration.TotalSeconds); var startTime = tileDuration.Multiply(numberOfTiles); // 1 second short of an hour - this is the test case var endTime = recordingDuration; var config = new LdSpectrogramConfig() { // same color map to reduce memory stress for test ColorMap1 = SpectrogramConstants.RGBMap_BGN_PMN_EVN, ColorMap2 = SpectrogramConstants.RGBMap_BGN_PMN_EVN, }; var generationData = new IndexGenerationData() { IndexCalculationDuration = dataScale, FrameLength = 512, FrameStep = 0, RecordingDuration = recordingDuration, }; var indexProperties = IndexProperties.GetIndexProperties(PathHelper.ResolveConfigFile("IndexPropertiesConfig.yml")); // create some fake spectra int duration = (int)(recordingDuration.TotalSeconds / dataScale.TotalSeconds); var spectra = new Dictionary <string, double[, ]>(); foreach (var key in config.GetKeys()) { var values = new double[256, duration].Fill(indexProperties[key].DefaultValue); spectra.Add(key, values); } string basename = "abc"; var image = ZoomCommon.DrawIndexSpectrogramCommon( config, generationData, indexProperties, startTime, endTime, dataScale, imageScale, ImageWidth, spectra, basename); // since we just asked for a fragment of the image at the end, // the expected width is just for that last fragment var lastWidth = expectedWidth - (numberOfTiles * ImageWidth); Assert.That.ImageIsSize(lastWidth, 256, image); Image <Rgb24> bitmap = (Image <Rgb24>)image; // test output for debugging //image.Save("./" + renderScale + ".png"); Assert.That.ImageRegionIsColor(new Rectangle(0, 0, lastWidth, 256), Color.Black, bitmap, 0); }
/// <summary> /// This method can add in absolute time if you want. /// Currently commented out - see below. /// </summary> public static Image <Rgb24> DrawIndexSpectrogramAtScale( LdSpectrogramConfig config, IndexGenerationData indexGenerationData, Dictionary <string, IndexProperties> indexProperties, TimeSpan focalTime, TimeSpan dataScale, TimeSpan imageScale, int imageWidth, Dictionary <string, double[, ]> spectra, string basename) { if (spectra == null) { LoggedConsole.WriteLine("WARNING: NO SPECTRAL DATA SUPPLIED"); return(null); } // check that scalingFactor >= 1.0 double scalingFactor = Math.Round(imageScale.TotalMilliseconds / dataScale.TotalMilliseconds); if (scalingFactor < 1.0) { LoggedConsole.WriteLine("WARNING: Scaling Factor < 1.0"); return(null); } Dictionary <string, IndexProperties> dictIp = indexProperties; dictIp = InitialiseIndexProperties.FilterIndexPropertiesForSpectralOnly(dictIp); // calculate start time by combining DatetimeOffset with minute offset. TimeSpan sourceMinuteOffset = indexGenerationData.AnalysisStartOffset; if (indexGenerationData.RecordingStartDate.HasValue) { DateTimeOffset dto = (DateTimeOffset)indexGenerationData.RecordingStartDate; sourceMinuteOffset = dto.TimeOfDay + sourceMinuteOffset; } // calculate data duration from column count of abitrary matrix var kvp = spectra.First(); var matrix = kvp.Value; //var matrix = spectra["ACI"]; // assume this key will always be present!! TimeSpan dataDuration = TimeSpan.FromSeconds(matrix.GetLength(1) * dataScale.TotalSeconds); TimeSpan recordingStartTime = TimeSpan.Zero; // default = zero minute of day i.e. midnight recordingStartTime = indexGenerationData.RecordingStartDate.Value.TimeOfDay.Add(indexGenerationData.AnalysisStartOffset); TimeSpan offsetTime = TimeSpan.Zero; TimeSpan imageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks); TimeSpan halfImageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks / 2); TimeSpan startTime = TimeSpan.Zero; if (focalTime != TimeSpan.Zero) { startTime = focalTime - halfImageDuration; } if (startTime < TimeSpan.Zero) { offsetTime = TimeSpan.Zero - startTime; startTime = TimeSpan.Zero; } TimeSpan endTime = imageDuration; if (focalTime != TimeSpan.Zero) { endTime = focalTime + halfImageDuration; } if (endTime > dataDuration) { endTime = dataDuration; } TimeSpan spectrogramDuration = endTime - startTime; int spectrogramWidth = (int)(spectrogramDuration.Ticks / imageScale.Ticks); // get the plain unchromed spectrogram var ldfcSpectrogram = ZoomCommon.DrawIndexSpectrogramCommon( config, indexGenerationData, indexProperties, startTime, endTime, dataScale, imageScale, imageWidth, spectra, basename); if (ldfcSpectrogram == null) { LoggedConsole.WriteLine("WARNING: NO SPECTROGRAM AT SCALE " + imageScale); return(null); } // now chrome spectrogram ldfcSpectrogram.Mutate(g2 => { // draw red line at focus time if (focalTime != TimeSpan.Zero) { Pen pen = new Pen(Color.Red, 1); TimeSpan focalOffset = focalTime - startTime; int x1 = (int)(focalOffset.Ticks / imageScale.Ticks); g2.DrawLine(pen, x1, 0, x1, ldfcSpectrogram.Height); } }); // draw the title bar int nyquist = 22050 / 2; // default if (indexGenerationData.SampleRateResampled > 0) { nyquist = indexGenerationData.SampleRateResampled / 2; } int herzInterval = 1000; if (config != null) { herzInterval = config.YAxisTicInterval; } string title = $"SCALE={imageScale.TotalSeconds}s/px. Duration={spectrogramDuration} "; //add chrome // NEXT LINE USED ONLY IF WANT ABSOLUTE TIME //startTime += recordingStartTime; var titleBar = DrawTitleBarOfZoomSpectrogram(title, ldfcSpectrogram.Width); ldfcSpectrogram = FrameZoomSpectrogram( ldfcSpectrogram, titleBar, startTime, imageScale, config.XAxisTicInterval, nyquist, herzInterval); // create the base canvas image on which to centre the focal image var image = Drawing.NewImage(imageWidth, ldfcSpectrogram.Height, Color.DarkGray); int xOffset = (int)(offsetTime.Ticks / imageScale.Ticks); image.Mutate(g1 => g1.DrawImage(ldfcSpectrogram, new Point(xOffset, 0), 1)); return(image); }
[DataRow(0.1, 35990)] // at this scale 10 pixels are missing... image is padded by 0 px, and misisng 10px public void TestImagesHaveCorrectLength(double renderScale, int expectedWidth) { // this value just represents a 'render as much as you can until this limit' threshold const int ImageWidth = 1024; var dataScale = 0.1.Seconds(); var imageScale = renderScale.Seconds(); var recordingDuration = 3599.Seconds(); // we simulate rendering the last tile at each resolution var tileDuration = imageScale.Multiply(ImageWidth); var numberOfTiles = (int)Math.Floor(recordingDuration.TotalSeconds / tileDuration.TotalSeconds); var startTime = tileDuration.Multiply(numberOfTiles); // 1 second short of an hour - this is the test case var endTime = recordingDuration; var config = new LdSpectrogramConfig(); var generationData = new IndexGenerationData() { IndexCalculationDuration = dataScale, FrameLength = 512, FrameStep = 0, RecordingDuration = recordingDuration, }; var indexProperties = IndexProperties.GetIndexProperties(PathHelper.ResolveConfigFile("IndexPropertiesConfig.yml")); // create some fake spectra int duration = (int)(recordingDuration.TotalSeconds / dataScale.TotalSeconds); var spectra = new Dictionary <string, double[, ]>(); foreach (var key in SpectralIndexValues.GetKeys()) { spectra.Add(key, new double[256, duration]); var bgnKey = nameof(SpectralIndexValues.BGN); if (key == bgnKey) { spectra[bgnKey].Fill(indexProperties[bgnKey].DefaultValue); } // due to a bug in DrawRgbColourMatrix the ACI calculation ends up being NaN. // This we fill one of our other spectra to a non-zero value to get the black colour we desire // Bug reference: https://github.com/QutEcoacoustics/audio-analysis/issues/154 var sumKey = nameof(SpectralIndexValues.SUM); if (key == sumKey) { // this forces ACI calculation to 0 / 1 instead of 0 / 0 spectra[sumKey].Fill(1.0); } } string basename = "abc"; var image = ZoomCommon.DrawIndexSpectrogramCommon( config, generationData, indexProperties, startTime, endTime, dataScale, imageScale, ImageWidth, spectra, basename); // since we just asked for a fragment of the image at the end, // the expected width is just for that last fragment var lastWidth = expectedWidth - (numberOfTiles * ImageWidth); Assert.That.ImageIsSize(lastWidth, 256, image); Bitmap bitmap = (Bitmap)image; // test output for debugging //image.Save("./" + renderScale + ".png"); Assert.That.ImageRegionIsColor(new Rectangle(0, 0, lastWidth, 256), Color.Black, bitmap, 0); }