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));
        }
예제 #2
0
        [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);
        }