/// <summary>
        /// This method can add in the absolute recording start time. However currently disabled.
        /// </summary>
        /// <param name="config">v.</param>
        /// <param name="indexGenerationData">indexGenerationData.</param>
        /// <param name="startTimeOfData">startTimeOfData.</param>
        /// <param name="compressionFactor">compressionFactor.</param>
        /// <param name="frameData">frameData.</param>
        /// <param name="indexData">indexData.</param>
        /// <param name="focalTime">focalTime.</param>
        /// <param name="frameScale">frameScale.</param>
        /// <param name="imageWidth">imageWidth.</param>
        public static Image DrawFrameSpectrogramAtScale(
            LdSpectrogramConfig config,
            IndexGenerationData indexGenerationData,
            TimeSpan startTimeOfData,
            int compressionFactor,
            List <double[]> frameData,
            double[,] indexData,
            TimeSpan focalTime,
            TimeSpan frameScale,
            int imageWidth)
        {
            if (frameData == null || frameData.Count == 0)
            {
                LoggedConsole.WriteLine("WARNING: NO SPECTRAL SPECTROGRAM DATA SUPPLIED");
                return(null);
            }

            // var recordingStartTime = TimeSpan.Zero; // default = zero minute of day i.e. midnight
            // var recordingStartTime = TimeTools.DateTimePlusTimeSpan(indexGenerationData.RecordingStartDate, indexGenerationData.AnalysisStartOffset);

            TimeSpan imageScale        = TimeSpan.FromTicks(frameScale.Ticks * compressionFactor);
            TimeSpan imageDuration     = TimeSpan.FromTicks(imageWidth * imageScale.Ticks);
            TimeSpan halfImageDuration = TimeSpan.FromTicks(imageWidth * imageScale.Ticks / 2);
            TimeSpan startTime         = focalTime - halfImageDuration;

            if (startTime < TimeSpan.Zero)
            {
                startTime = TimeSpan.Zero;
            }

            int             startIndex         = (int)((startTime.Ticks - startTimeOfData.Ticks) / frameScale.Ticks);
            int             requiredFrameCount = imageWidth * compressionFactor;
            List <double[]> frameSelection     = frameData.GetRange(startIndex, requiredFrameCount);

            double[,] spectralSelection = MatrixTools.ConvertList2Matrix(frameSelection);

            // compress spectrograms to correct scale
            if (compressionFactor > 1)
            {
                spectralSelection = TemporalMatrix.CompressFrameSpectrograms(spectralSelection, compressionFactor);
            }

            var spectrogramImage = DrawStandardSpectrogramInFalseColour(spectralSelection);

            int x1 = (int)(halfImageDuration.Ticks / imageScale.Ticks);

            spectrogramImage.Mutate(g2 =>
            {
                // draw focus time on image
                if (focalTime != TimeSpan.Zero)
                {
                    Pen pen = new Pen(Color.Red, 1);
                    g2.DrawLine(pen, x1, 0, x1, spectrogramImage.Height);
                }
            });

            int nyquist = 22050 / 2; // default

            if (indexGenerationData.SampleRateResampled > 0)
            {
                nyquist = indexGenerationData.SampleRateResampled / 2;
            }

            int    herzInterval = config.YAxisTicInterval;
            string title        = $"ZOOM SCALE={imageScale.TotalMilliseconds}ms/pixel   Image duration={imageDuration} ";
            var    titleBar     = DrawTitleBarOfZoomSpectrogram(title, spectrogramImage.Width);

            // add the recording start time ONLY IF WANT ABSOLUTE TIME SCALE - obtained from info in file name
            // startTime += recordingStartTime;
            spectrogramImage = FrameZoomSpectrogram(spectrogramImage, titleBar, startTime, imageScale, config.XAxisTicInterval, nyquist, herzInterval);

            // MAY WANT THESE CLIPPING TRACKS AT SOME POINT
            // read high amplitude and clipping info into an image
            //string indicesFile = Path.Combine(configuration.InputDirectoryInfo.FullName, fileStem + ".csv");
            //string indicesFile = Path.Combine(config.InputDirectoryInfo.FullName, fileStem + ".Indices.csv");
            //string indicesFile = Path.Combine(configuration.InputDirectoryInfo.FullName, fileStem + "_" + configuration.AnalysisType + ".csv");
            //Image imageX = DrawSummaryIndices.DrawHighAmplitudeClippingTrack(indicesFile.ToFileInfo());
            //if (null != imageX) imageX.Save(Path.Combine(outputDirectory.FullName, fileStem + ".ClipHiAmpl.png"));

            // create the base image
            Image image = new Image <Rgb24>(imageWidth, spectrogramImage.Height);

            image.Mutate(g1 =>
            {
                g1.Clear(Color.DarkGray);

                //int xOffset = (int)(startTime.Ticks / imageScale.Ticks);
                int xOffset = (imageWidth / 2) - x1;
                g1.DrawImage(spectrogramImage, new Point(xOffset, 0), 1);
            });

            return(image);
        }
        /// <summary>
        /// Assume that we are processing data for one minute only.
        ///     From this one minute of data, we produce images at three scales.
        ///     A one minute recording framed at 20ms should yield 3000 frames.
        ///     But to achieve this where sr= 22050 and frameSize=512, we need an overlap of 71 samples.
        ///     Consequently only 2999 frames returned per minute.
        ///     Therefore have to pad end to get 3000 frames.
        /// </summary>
        public static TimeOffsetSingleLayerSuperTile[] DrawSuperTilesFromSingleFrameSpectrogram(DirectoryInfo dataDir, LdSpectrogramConfig analysisConfig, Dictionary <string, IndexProperties> indexProperties, SpectrogramZoomingConfig zoomingConfig, int minute, double[] imageScales, string basename, IndexGenerationData indexGeneration, ImageChrome chromeOption, TimeSpan alignmentPadding)
        {
            string fileStem = basename;

            // string analysisType = analysisConfig.AnalysisType;
            TimeSpan indexScale = indexGeneration.IndexCalculationDuration;
            TimeSpan frameScale = TimeSpan.FromSeconds(zoomingConfig.SpectralFrameDuration);

            var expectedDataDurationInSeconds = (int)indexGeneration.MaximumSegmentDuration.Value.TotalSeconds;
            var expectedFrameCount            = (int)Math.Round(expectedDataDurationInSeconds / zoomingConfig.SpectralFrameDuration);

            string fileName        = fileStem + "_" + minute + "min.csv";
            string csvPath         = Path.Combine(dataDir.FullName, fileName);
            bool   skipHeader      = true;
            bool   skipFirstColumn = true;

            // read spectrogram into a list of frames
            List <double[]> frameList = CsvTools.ReadCSVFileOfDoubles(csvPath, skipHeader, skipFirstColumn);

            if (frameList == null)
            {
                LoggedConsole.WriteErrorLine(
                    "WARNING: METHOD DrawSuperTilesFromSingleFrameSpectrogram(): NO SPECTRAL DATA SUPPLIED");
                return(null);
            }

            PadEndOfListOfFrames(frameList, expectedFrameCount);
            TrimEndOfListOfFrames(frameList, expectedFrameCount);

            //// frame count will be one less than expected for the recording segment because of frame overlap
            //// Therefore pad the end of the list of frames with the last frame.
            // int frameDiscrepancy = expectedFrameCount - frameList.Count;
            // if (frameDiscrepancy > 0)
            // {
            // double[] frame = frameList[frameList.Count - 1];
            // for (int d = 0; d < frameDiscrepancy; d++)
            // {
            // frameList.Add(frame);
            // }
            // }
            var frameData = new TemporalMatrix("rows", MatrixTools.ConvertList2Matrix(frameList), frameScale);

            frameData.SwapTemporalDimension();                              // so the two data matrices have the same temporal dimension

            TimeSpan startTime       = indexGeneration.AnalysisStartOffset; // default = zero minute of day i.e. midnight
            TimeSpan startTimeOfData = startTime + TimeSpan.FromMinutes(minute);

            var str = new TimeOffsetSingleLayerSuperTile[imageScales.Length];

            // make the images
            for (int scale = 0; scale < imageScales.Length; scale++)
            {
                TimeSpan imageScale        = TimeSpan.FromSeconds(imageScales[scale]);
                var      compressionFactor =
                    (int)Math.Round(imageScale.TotalMilliseconds / frameData.DataScale.TotalMilliseconds);
                double columnDuration = imageScale.TotalSeconds;

                // int expectedFrameCount = (int)Math.Round(expectedDataDurationInSeconds / columnDuration);

                // ############## RESEARCH CHOICE HERE >>>>  compress spectrograms to correct scale using either max or average
                // Average appears to offer better contrast.
                // double[,] data = frameData.CompressMatrixInTemporalDirectionByTakingMax(imageScale);
                double[,] data = frameData.CompressMatrixInTemporalDirectionByTakingAverage(imageScale);

                var spectrogramImage = DrawFrameSpectrogramAtScale(
                    analysisConfig,
                    zoomingConfig,
                    startTimeOfData,
                    imageScale,
                    data,
                    indexGeneration,
                    chromeOption);

                str[scale] = new TimeOffsetSingleLayerSuperTile(
                    alignmentPadding,
                    SpectrogramType.Frame,
                    imageScale,
                    spectrogramImage.CloneAs <Rgba32>(),
                    startTimeOfData);
            }

            return(str);
        }