Example #1
0
        public static Image <Rgb24> FrameSliceOf3DSpectrogram_DayOfYear(Image <Rgb24> bmp1, Image <Rgb24> titleBar, int year, int dayOfYear, TimeSpan xInterval, int herzValue, FileInfo sunriseSetData, int nyquistFreq)
        {
            Image <Rgb24> suntrack = SunAndMoon.AddSunTrackToImage(bmp1.Width, sunriseSetData, year, dayOfYear);

            bmp1.Mutate(g =>
            {
                Pen pen        = new Pen(Color.White, 1);
                var stringFont = Drawing.Arial12;

                //Font stringFont = Drawing.Tahoma9;

                DateTime theDate  = new DateTime(year, 1, 1).AddDays(dayOfYear - 1);
                string dateString = $"{year} {DataTools.MonthNames[theDate.Month - 1]} {theDate.Day:d2}";
                g.DrawText(dateString, stringFont, Color.Wheat, new PointF(10, 3));
            });

            TimeSpan xAxisPixelDuration = TimeSpan.FromSeconds(60);
            var      minuteOffset       = TimeSpan.Zero;
            double   secondsDuration    = xAxisPixelDuration.TotalSeconds * bmp1.Width;
            TimeSpan fullDuration       = TimeSpan.FromSeconds(secondsDuration);

            // init frequency scale
            int herzInterval = 1000;
            int frameSize    = bmp1.Height;
            var freqScale    = new DSP.FrequencyScale(nyquistFreq, frameSize, herzInterval);

            SpectrogramTools.DrawGridLinesOnImage((Image <Rgb24>)bmp1, minuteOffset, fullDuration, xInterval, freqScale);

            int trackHeight      = 20;
            int imageHt          = bmp1.Height + trackHeight + trackHeight + trackHeight;
            var xAxisTicInterval = TimeSpan.FromMinutes(60); // assume 60 pixels per hour
            var timeScale24Hour  = ImageTrack.DrawTimeTrack(fullDuration, minuteOffset, xAxisTicInterval, bmp1.Width, trackHeight, "hours");

            var imageList = new List <Image <Rgb24> >
            {
                titleBar,
                timeScale24Hour,
                suntrack,
                bmp1,
                timeScale24Hour
            };
            var compositeBmp = ImageTools.CombineImagesVertically(imageList);

            // trackHeight = compositeBmp.Height;
            // Image<Rgb24> timeScale12Months = ImageTrack.DrawYearScaleVertical(40, trackHeight);
            // Image<Rgb24> freqScale = DrawFreqScale_vertical(40, trackHeight, HerzValue, nyquistFreq);

            imageList = new List <Image <Rgb24> >();

            // imageList.Add(timeScale12Months);
            imageList.Add(compositeBmp);

            // imageList.Add(freqScale);
            compositeBmp = ImageTools.CombineImagesInLine(imageList.ToArray());

            return(compositeBmp);
        }
Example #2
0
        public static Image <Rgb24> ConcatenateFourChannelImages(FileInfo[] imageFiles, DirectoryInfo imageDirectory, string fileSuffix, string date)
        {
            // get first image to find its dimensions
            var image = (Image <Rgb24>)Image.Load(imageFiles[0].FullName);

            var  brush      = Color.White;
            Font stringFont = Drawing.Tahoma12;

            //create spacer image
            int           width       = 1;
            int           height      = image.Height;
            Image <Rgb24> spacerImage = new Image <Rgb24>(width, height);

            spacerImage.Mutate(g =>
            {
                g.Clear(Color.DarkGray);
            });

            // init output list of images
            var fourChannelList = new List <Image <Rgb24> >();

            for (int channel = 0; channel < 4; channel++)
            {
                var imageList = new List <Image <Rgb24> >();

                //   Monitoring_Rosin_20120329T000000 + 0200_.merged.wav.channel_0__2Maps.png;
                string fileMatch = $@"0000+0200_.merged.wav.channel_{channel}__{fileSuffix}";

                foreach (FileInfo imageFile in imageFiles)
                {
                    if (!imageFile.Name.EndsWith(fileMatch))
                    {
                        continue;
                    }

                    image = (Image <Rgb24>)Image.Load(imageFile.FullName);
                    imageList.Add(image);
                    imageList.Add(spacerImage);
                }

                imageList.Add(spacerImage);
                imageList.Add(spacerImage);
                var concatImage = ImageTools.CombineImagesInLine(imageList);
                concatImage.Mutate(g =>
                {
                    string chn = $"ch{channel + 1}";
                    g.DrawTextSafe(chn, stringFont, brush, new PointF(2, 40));
                });

                fourChannelList.Add(concatImage);
            }

            var combinedImage = ImageTools.CombineImagesVertically(fourChannelList);

            return(combinedImage);
        }
Example #3
0
        /// <summary>
        /// Creates an image from the frequency/oscillations matrix.
        /// The y-axis scale = frequency bins as per normal spectrogram.
        /// The x-axis scale is oscillations per second.
        /// </summary>
        /// <param name="freqOscilMatrix">the input frequency/oscillations matrix</param>
        /// <param name="framesPerSecond">to give the time scale</param>
        /// <param name="freqBinWidth">to give the frequency scale</param>
        /// <param name="sampleLength">to allow calculation of the oscillations scale</param>
        /// <param name="algorithmName">the algorithm used to compute the oscillations.</param>
        /// <returns>bitmap image</returns>
        public static Image GetFreqVsOscillationsImage(double[,] freqOscilMatrix, double framesPerSecond, double freqBinWidth, int sampleLength, string algorithmName)
        {
            // remove the high cycles/sec end of the matrix because nothing really happens here.
            int maxRows = freqOscilMatrix.GetLength(0) / 2;

            freqOscilMatrix = MatrixTools.Submatrix(freqOscilMatrix, 0, 0, maxRows - 1, freqOscilMatrix.GetLength(1) - 1);

            // get the OSC spectral index
            var spectralIndex = MatrixTools.GetMaximumColumnValues(freqOscilMatrix);

            // Convert spectrum index to oscillations per second
            double oscillationBinWidth = framesPerSecond / sampleLength;

            //draw an image
            freqOscilMatrix = MatrixTools.MatrixRotate90Anticlockwise(freqOscilMatrix);

            // each value is to be drawn as a 5 pixel x 5 pixel square
            int xscale = 5;
            int yscale = 5;

            if (maxRows < 10)
            {
                xscale = 10;
            }

            //var image1 = ImageTools.DrawMatrixInColour(freqOscilMatrix, xPixelsPerCell: xscale, yPixelsPerCell: yscale);
            //var image2 = ImageTools.DrawVectorInColour(DataTools.reverseArray(spectralIndex), cellWidth: xscale);
            var image1 = ImageTools.DrawMatrixInGrayScale(freqOscilMatrix, xPixelsPerCell: xscale, yPixelsPerCell: yscale, reverse: true);

            spectralIndex = DataTools.NormaliseByScalingMaxValueToOne(spectralIndex);
            var image2 = ImageTools.DrawVectorInGrayScaleWithoutNormalisation(DataTools.reverseArray(spectralIndex), xscale, yscale, reverse: true);

            var image = ImageTools.CombineImagesInLine(new[] { image1, image2 });

            // place a grid line every 5 cycles per second.
            double cycleInterval = 5.0;
            double xTicInterval  = cycleInterval / oscillationBinWidth * xscale;

            // a tic every 1000 Hz.
            int    herzInterval = 1000;
            double yTicInterval = herzInterval / freqBinWidth * yscale;

            int xOffset = xscale / 2;
            int yOffset = yscale / 2;

            image = ImageTools.DrawYaxisScale(image, 10, herzInterval, yTicInterval, yOffset);
            image = ImageTools.DrawXaxisScale(image, 15, cycleInterval, xTicInterval, 10, -xOffset);

            var titleBar  = DrawTitleBarOfOscillationSpectrogram(algorithmName, image.Width);
            var imageList = new List <Image> {
                titleBar, image
            };
            var compositeBmp = (Bitmap)ImageTools.CombineImagesVertically(imageList);

            return(compositeBmp);
        }
Example #4
0
        public static Image FrameSliceOf3DSpectrogram_DayOfYear(Image bmp1, Image titleBar, int year, int dayOfYear, TimeSpan xInterval, int herzValue, FileInfo sunriseSetData, int nyquistFreq)
        {
            Bitmap suntrack = SunAndMoon.AddSunTrackToImage(bmp1.Width, sunriseSetData, year, dayOfYear);

            Graphics g          = Graphics.FromImage(bmp1);
            Pen      pen        = new Pen(Color.White);
            Font     stringFont = new Font("Arial", 12);

            //Font stringFont = new Font("Tahoma", 9);

            DateTime theDate    = new DateTime(year, 1, 1).AddDays(dayOfYear - 1);
            string   dateString = string.Format("{0} {1} {2:d2}", year, DataTools.MonthNames[theDate.Month - 1], theDate.Day);

            g.DrawString(dateString, stringFont, Brushes.Wheat, new PointF(10, 3));

            TimeSpan xAxisPixelDuration = TimeSpan.FromSeconds(60);
            var      minuteOffset       = TimeSpan.Zero;
            double   secondsDuration    = xAxisPixelDuration.TotalSeconds * bmp1.Width;
            TimeSpan fullDuration       = TimeSpan.FromSeconds(secondsDuration);

            // init frequency scale
            int herzInterval = 1000;
            int frameSize    = bmp1.Height;
            var freqScale    = new DSP.FrequencyScale(nyquistFreq, frameSize, herzInterval);

            SpectrogramTools.DrawGridLinesOnImage((Bitmap)bmp1, minuteOffset, fullDuration, xInterval, freqScale);

            int trackHeight      = 20;
            int imageHt          = bmp1.Height + trackHeight + trackHeight + trackHeight;
            var xAxisTicInterval = TimeSpan.FromMinutes(60); // assume 60 pixels per hour
            var timeScale24Hour  = ImageTrack.DrawTimeTrack(fullDuration, minuteOffset, xAxisTicInterval, bmp1.Width, trackHeight, "hours");

            var imageList = new List <Image>();

            imageList.Add(titleBar);
            imageList.Add(timeScale24Hour);
            imageList.Add(suntrack);
            imageList.Add(bmp1);
            imageList.Add(timeScale24Hour);
            Image compositeBmp = ImageTools.CombineImagesVertically(imageList.ToArray());

            // trackHeight = compositeBmp.Height;
            // Bitmap timeScale12Months = ImageTrack.DrawYearScaleVertical(40, trackHeight);
            // Bitmap freqScale = DrawFreqScale_vertical(40, trackHeight, HerzValue, nyquistFreq);

            imageList = new List <Image>();

            // imageList.Add(timeScale12Months);
            imageList.Add(compositeBmp);

            // imageList.Add(freqScale);
            compositeBmp = ImageTools.CombineImagesInLine(imageList.ToArray());

            return(compositeBmp);
        }
Example #5
0
        /// <summary>
        /// Generates the FREQUENCY x OSCILLATIONS Graphs and csv
        /// I have experimented with five methods to search for oscillations:
        ///  1: string algorithmName = "Autocorr-FFT";
        ///     use this if want more detailed output - but not necessrily accurate!
        ///  2: string algorithmName = "Autocorr-SVD-FFT";
        ///     use this if want only dominant oscillations
        ///  3: string algorithmName = "Autocorr-Cwt";
        ///     a Wavelets option but could not get it to work well
        ///  4: string algorithmName = "Autocorr-WPD";
        ///     another Wavelets option but could not get it to work well
        ///  5: Discrete Cosine Transform
        ///     The DCT only works well when you know which periodicity you are looking for. e.g. Canetoad.
        /// </summary>
        public static Tuple <Image <Rgb24>, double[, ]> GenerateOscillationDataAndImages(FileInfo audioSegment, Dictionary <string, string> configDict, bool drawImage = false)
        {
            // set two oscillation detection parameters
            double sensitivity = DefaultSensitivityThreshold;

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SensitivityThreshold))
            {
                sensitivity = double.Parse(configDict[AnalysisKeys.OscilDetection2014SensitivityThreshold]);
            }

            // Sample length i.e. number of frames spanned to calculate oscillations per second
            int sampleLength = DefaultSampleLength;

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SampleLength))
            {
                sampleLength = int.Parse(configDict[AnalysisKeys.OscilDetection2014SampleLength]);
            }

            var sonoConfig = new SonogramConfig(configDict); // default values config

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014FrameSize))
            {
                sonoConfig.WindowSize = int.Parse(configDict[AnalysisKeys.OscilDetection2014FrameSize]);
            }

            var recordingSegment = new AudioRecording(audioSegment.FullName);
            var spgm             = GetSpectrogramMatrix(recordingSegment, DefaultFrameLength);

            double framesPerSecond = DefaultResampleRate / (double)DefaultFrameLength;
            double freqBinWidth    = framesPerSecond;

            // get the appropriate sampleLength (patch size) for short recordings
            int framecount = spgm.GetLength(0);

            sampleLength = AdjustSampleSize(framecount, sampleLength);

            var algorithmName1   = "autocorr-svd-fft";
            var freqOscilMatrix1 = GetFrequencyByOscillationsMatrix(spgm, sensitivity, sampleLength, algorithmName1);
            var image1           = GetFreqVsOscillationsImage(freqOscilMatrix1, framesPerSecond, freqBinWidth, sampleLength, algorithmName1);

            var algorithmName2   = "autocorr-fft";
            var freqOscilMatrix2 = GetFrequencyByOscillationsMatrix(spgm, sensitivity, sampleLength, algorithmName2);
            var image2           = GetFreqVsOscillationsImage(freqOscilMatrix2, framesPerSecond, freqBinWidth, sampleLength, algorithmName2);

            //IMPORTANT NOTE: To generate an OSC spectral index matrix for making LDFC spectrograms, use the following line:
            //var spectralIndex = MatrixTools.GetMaximumColumnValues(freqOscilMatrix2);

            var compositeImage = ImageTools.CombineImagesInLine(image2, image1);

            // Return (1) composite image of oscillations,
            //        (2) data matrix from only one algorithm,
            return(Tuple.Create(compositeImage, freqOscilMatrix2));
        }
        public static Image ConcatenateFourChannelImages(FileInfo[] imageFiles, DirectoryInfo imageDirectory, string fileSuffix, string date)
        {
            // get first image to find its dimensions
            Image image = Image.FromFile(imageFiles[0].FullName);

            Brush brush      = Brushes.White;
            Font  stringFont = new Font("Tahoma", 12);

            //create spacer image
            int      width       = 1;
            int      height      = image.Height;
            Bitmap   spacerImage = new Bitmap(width, height);
            Graphics g           = Graphics.FromImage(spacerImage);

            g.Clear(Color.DarkGray);

            // init output list of images
            var fourChannelList = new List <Image>();

            for (int channel = 0; channel < 4; channel++)
            {
                var imageList = new List <Image>();

                //   Monitoring_Rosin_20120329T000000 + 0200_.merged.wav.channel_0__2Maps.png;
                string fileMatch = string.Format(@"0000+0200_.merged.wav.channel_{0}__{1}", channel, fileSuffix);

                foreach (FileInfo imageFile in imageFiles)
                {
                    if (!imageFile.Name.EndsWith(fileMatch))
                    {
                        continue;
                    }

                    image = Image.FromFile(imageFile.FullName);
                    imageList.Add(image);
                    imageList.Add(spacerImage);
                }

                imageList.Add(spacerImage);
                imageList.Add(spacerImage);
                Image concatImage = ImageTools.CombineImagesInLine(imageList);
                g = Graphics.FromImage(concatImage);
                string chn = string.Format("ch{0}", channel + 1);
                g.DrawString(chn, stringFont, brush, new PointF(2, 40));

                fourChannelList.Add(concatImage);
            }

            Image combinedImage = ImageTools.CombineImagesVertically(fourChannelList);

            return(combinedImage);
        }
        public void TestCombineImagesInLine()
        {
            var actual = ImageTools.CombineImagesInLine(
                null,
                Drawing.NewImage(10, 100, Color.Red),
                Drawing.NewImage(100, 100, Color.Red),
                Drawing.NewImage(20, 100, Color.Red),
                null,
                Drawing.NewImage(200, 100, Color.Red),
                null);

            Assert.That.ImageIsSize(330, 100, actual);
            Assert.That.ImageRegionIsColor(actual.Bounds(), Color.Red, actual);
        }
Example #8
0
        public static Image FrameSliceOf3DSpectrogram_ConstantFreq(Image bmp1, Image titleBar, TimeSpan xInterval, int herzValue, FileInfo sunriseSetData, int nyquistFreq)
        {
            SunAndMoon.AddSunRiseSetLinesToImage((Bitmap)bmp1, sunriseSetData, 0, 365, 1); // assume full year and 1px/day

            var g          = Graphics.FromImage(bmp1);
            var pen        = new Pen(Color.White);
            var stringFont = new Font("Arial", 12);
            var str        = $"Freq = {herzValue} Hz";

            g.DrawString(str, stringFont, Brushes.Wheat, new PointF(10, 7));

            var    xAxisPixelDuration = TimeSpan.FromSeconds(60);
            var    startOffset        = TimeSpan.Zero;
            double secondsDuration    = xAxisPixelDuration.TotalSeconds * bmp1.Width;
            var    fullDuration       = TimeSpan.FromSeconds(secondsDuration);

            // init frequency scale
            int herzInterval = 1000;
            int frameSize    = bmp1.Height;
            var freqScale    = new DSP.FrequencyScale(nyquistFreq, frameSize, herzInterval);

            SpectrogramTools.DrawGridLinesOnImage((Bitmap)bmp1, startOffset, fullDuration, xInterval, freqScale);

            int trackHeight      = 20;
            var xAxisTicInterval = TimeSpan.FromMinutes(60); // assume 60 pixels per hour
            var timeScale24Hour  = ImageTrack.DrawTimeTrack(fullDuration, startOffset, xAxisTicInterval, bmp1.Width, trackHeight, "hours");

            var imageList = new List <Image> {
                titleBar, timeScale24Hour, bmp1, timeScale24Hour
            };
            var compositeBmp = ImageTools.CombineImagesVertically(imageList.ToArray());

            if (compositeBmp == null)
            {
                throw new ArgumentNullException(nameof(compositeBmp));
            }

            trackHeight = compositeBmp.Height;
            Bitmap timeScale12Months = ImageTrack.DrawYearScaleVertical(40, trackHeight);
            Bitmap freqScaleImage    = DrawFreqScale_vertical(40, trackHeight, herzValue, nyquistFreq);

            imageList = new List <Image> {
                timeScale12Months, compositeBmp, freqScaleImage
            };
            compositeBmp = ImageTools.CombineImagesInLine(imageList.ToArray());

            return(compositeBmp);
        }
        public void TestCombineImagesInLineDefaultFill()
        {
            var actual = ImageTools.CombineImagesInLine(
                null,
                Drawing.NewImage(100, 80, Color.Red),
                Drawing.NewImage(100, 100, Color.Red),
                Drawing.NewImage(100, 80, Color.Red),
                null,
                Drawing.NewImage(100, 100, Color.Red),
                null);

            Assert.That.ImageIsSize(400, 100, actual);
            Assert.That.ImageRegionIsColor((0, 0, 400, 80).AsRect(), Color.Red, actual);

            Assert.That.ImageRegionIsColor((0, 80, 100, 20).AsRect(), Color.Black, actual);
            Assert.That.ImageRegionIsColor((100, 80, 100, 20).AsRect(), Color.Red, actual);
            Assert.That.ImageRegionIsColor((200, 80, 100, 20).AsRect(), Color.Black, actual);
            Assert.That.ImageRegionIsColor((300, 80, 100, 20).AsRect(), Color.Red, actual);
        }
        public static Image GetFreqVsOscillationsImage(double[,] freqOscilMatrix, double framesPerSecond, double freqBinWidth, int sampleLength, string algorithmName)
        {
            // remove the high cycles/sec end of the matrix because nothing really happens here.
            freqOscilMatrix = MatrixTools.Submatrix(freqOscilMatrix, 0, 0, 30, freqOscilMatrix.GetLength(1) - 1);

            // get the OSC spectral index
            // double[] spectralIndex = ConvertMatrix2SpectralIndexBySummingFreqColumns(freqOscilMatrix, skipNrows: 0);
            var spectralIndex = MatrixTools.GetMaximumColumnValues(freqOscilMatrix);

            // Convert spectrum index to oscillations per second
            double oscillationBinWidth = framesPerSecond / sampleLength;

            //draw an image
            freqOscilMatrix = MatrixTools.MatrixRotate90Anticlockwise(freqOscilMatrix);
            int xscale = 5;
            int yscale = 5;
            var image1 = ImageTools.DrawMatrixInColour(freqOscilMatrix, xPixelsPerCell: xscale, yPixelsPerCell: yscale);

            var image2 = ImageTools.DrawVectorInColour(DataTools.reverseArray(spectralIndex), cellWidth: xscale);

            var image = ImageTools.CombineImagesInLine(new[] { image1, image2 });

            // a tic every 5cpsec.
            double cycleInterval = 5.0;
            double xTicInterval  = cycleInterval / oscillationBinWidth * xscale;

            // a tic every 1000 Hz.
            int    herzInterval = 1000;
            double yTicInterval = herzInterval / freqBinWidth * yscale;
            int    xOffset      = xscale / 2;
            int    yOffset      = yscale / 2;

            image = ImageTools.DrawXandYaxes(image, 18, cycleInterval, xTicInterval, xOffset, herzInterval, yTicInterval, yOffset);
            var titleBar  = DrawTitleBarOfOscillationSpectrogram(algorithmName, image.Width);
            var imageList = new List <Image> {
                titleBar, image
            };
            var compositeBmp = (Bitmap)ImageTools.CombineImagesVertically(imageList);

            return(compositeBmp);
        }
Example #11
0
        /// <summary>
        /// Can be used for visual checking and debugging purposes.
        /// </summary>
        public static void DrawNormalisedIndexMatrices(DirectoryInfo dir, string baseName, Dictionary <string, double[, ]> dictionary)
        {
            var list = new List <Image>();

            foreach (string key in ContentSignatures.IndexNames)
            {
                var bmp = ImageTools.DrawReversedMatrixWithoutNormalisation(dictionary[key]);

                // need to rotate spectrogram to get correct orientation.
                bmp.RotateFlip(RotateFlipType.Rotate270FlipNone);

                // draw grid lines and add axis scales
                var xAxisPixelDuration = TimeSpan.FromSeconds(60);
                var fullDuration       = TimeSpan.FromTicks(xAxisPixelDuration.Ticks * bmp.Width);
                var freqScale          = new FrequencyScale(11025, 512, 1000);
                SpectrogramTools.DrawGridLinesOnImage((Bitmap)bmp, TimeSpan.Zero, fullDuration, xAxisPixelDuration, freqScale);
                const int trackHeight        = 20;
                var       recordingStartDate = default(DateTimeOffset);
                var       timeBmp            = ImageTrack.DrawTimeTrack(fullDuration, recordingStartDate, bmp.Width, trackHeight);
                var       array = new Image[2];
                array[0] = bmp;
                array[1] = timeBmp;
                var image = ImageTools.CombineImagesVertically(array);

                // add a header to the spectrogram
                var      header = new Bitmap(image.Width, 20);
                Graphics g      = Graphics.FromImage(header);
                g.Clear(Color.LightGray);
                g.SmoothingMode     = SmoothingMode.AntiAlias;
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.PixelOffsetMode   = PixelOffsetMode.HighQuality;
                g.DrawString(key, new Font("Tahoma", 9), Brushes.Black, 4, 4);
                list.Add(ImageTools.CombineImagesVertically(new List <Image>(new[] { header, image })));
            }

            // save the image - the directory for the path must exist
            var path       = Path.Combine(dir.FullName, baseName + "__Towsey.Acoustic.GreyScaleImages.png");
            var indexImage = ImageTools.CombineImagesInLine(list);

            indexImage?.Save(path);
        }
        /// <summary>
        /// Can be used for visual checking and debugging purposes.
        /// </summary>
        public static void DrawNormalisedIndexMatrices(DirectoryInfo dir, string baseName, Dictionary <string, double[, ]> dictionary)
        {
            var list = new List <Image <Rgb24> >();

            foreach (string key in ContentSignatures.IndexNames)
            {
                var bmp = ImageTools.DrawReversedMatrixWithoutNormalisation(dictionary[key]);

                // need to rotate spectrogram to get correct orientation.
                bmp.RotateFlip(RotateFlipType.Rotate270FlipNone);

                // draw grid lines and add axis scales
                var xAxisPixelDuration = TimeSpan.FromSeconds(60);
                var fullDuration       = TimeSpan.FromTicks(xAxisPixelDuration.Ticks * bmp.Width);
                var freqScale          = new FrequencyScale(11025, 512, 1000);
                SpectrogramTools.DrawGridLinesOnImage((Image <Rgb24>)bmp, TimeSpan.Zero, fullDuration, xAxisPixelDuration, freqScale);
                const int trackHeight        = 20;
                var       recordingStartDate = default(DateTimeOffset);
                var       timeBmp            = ImageTrack.DrawTimeTrack(fullDuration, recordingStartDate, bmp.Width, trackHeight);

                var image = ImageTools.CombineImagesVertically(bmp, timeBmp);

                // add a header to the spectrogram
                var header = Drawing.NewImage(image.Width, 20, Color.LightGray);
                header.Mutate(g =>
                {
                    g.DrawText(key, Drawing.Tahoma9, Color.Black, new PointF(4, 4));
                    list.Add(ImageTools.CombineImagesVertically(header, image));
                });
            }

            // save the image - the directory for the path must exist
            var path       = Path.Combine(dir.FullName, baseName + "__Towsey.Acoustic.GreyScaleImages.png");
            var indexImage = ImageTools.CombineImagesInLine(list);

            indexImage?.Save(path);
        }
        // ##############################################################################################################
        // ######################### ORIGINAL METHOD FOR STITCHING  Gianna Pavan's DATA (10 minutes every 30 minutes)

        /// <summary>
        /// This method stitches together spectrogram images derived from consecutive shorter recordings over a 24 hour period.
        /// Currently set for the recording protocol of Gianna Pavan (10 minutes every 30 minutes).
        ///
        /// Call this method from Sandpit or where ever!
        ///
        /// IMPORTANT NOTE: This method does NOT check to see if the images are in temporal order.
        ///                 A SORT line should be inserted somewhere
        /// </summary>
        public static void StitchPartialSpectrograms()
        {
            //######################################################
            // ********************* set the below parameters
            var    inputDirectory  = new DirectoryInfo(@"Z:\Italy_GianniPavan\output4\Towsey.Acoustic");
            string opFileStem      = "Sassofratino_24hours_v3";
            var    outputDirectory = new DirectoryInfo(@"Z:\Italy_GianniPavan\output4\");

            // a filter to select images to be stitched
            string endString = "_000.2MAPS.png";

            // recording protocol
            int      minutesBetweenRecordingStarts = 30;
            TimeSpan minOffset = TimeSpan.Zero; // assume first recording in sequence started at midnight

            // X-axis timescale
            int pixelColumnsPerHour = 60;
            int trackHeight         = IndexDisplay.DefaultTrackHeight;

            // ********************* set the above parameters
            //######################################################

            string[] fileEntries       = Directory.GetFiles(inputDirectory.FullName);
            var      images            = new List <Image>();
            bool     interpolateSpacer = true;
            var      imagePair         = new Image[2];

            TimeSpan xAxisTicInterval = TimeSpan.FromMinutes(pixelColumnsPerHour); // assume 60 pixels per hour

            // loop through all files in the required directory
            foreach (string path in fileEntries)
            {
                // filter files.
                if (!path.EndsWith(endString))
                {
                    continue;
                }

                var image       = new Bitmap(path);
                int spacerWidth = minutesBetweenRecordingStarts - image.Width;

                if (interpolateSpacer)
                {
                    var spacer = new Bitmap(spacerWidth, image.Height);
                    imagePair[0] = image;
                    imagePair[1] = spacer;
                    image        = (Bitmap)ImageTools.CombineImagesInLine(imagePair);
                }

                images.Add(image);
            }

            var compositeBmp = ImageTools.CombineImagesInLine(images.ToArray());

            var fullDuration = TimeSpan.FromMinutes(compositeBmp.Width);
            var timeBmp      = ImageTrack.DrawTimeTrack(fullDuration, minOffset, xAxisTicInterval, compositeBmp.Width, trackHeight, "hours");

            var gr         = Graphics.FromImage(compositeBmp);
            int halfHeight = compositeBmp.Height / 2;

            //add in the title bars
            string title    = $"24 hour FALSE-COLOUR SPECTROGRAM      (scale: hours x kHz)      (colour: R-G-B = BGN-AVG-CVR)         {Meta.OrganizationTag}  ";
            var    titleBmp = ImageTrack.DrawTitleTrack(compositeBmp.Width, trackHeight, title);
            int    offset   = 0;

            gr.DrawImage(titleBmp, 0, offset); //draw in the top time scale
            title    = $"24 hour FALSE-COLOUR SPECTROGRAM      (scale: hours x kHz)      (colour: R-G-B = ACI-ENT-EVN)         {Meta.OrganizationTag}  ";
            titleBmp = ImageTrack.DrawTitleTrack(compositeBmp.Width, trackHeight, title);
            offset   = halfHeight;
            gr.DrawImage(titleBmp, 0, offset); //draw in the top time scale

            //add in the timescale tracks
            offset = trackHeight;
            gr.DrawImage(timeBmp, 0, offset); //draw in the top time scale
            offset = compositeBmp.Height - trackHeight;
            gr.DrawImage(timeBmp, 0, offset); //draw in the top time scale
            offset = halfHeight - trackHeight;
            gr.DrawImage(timeBmp, 0, offset); //draw in the top time scale
            offset = halfHeight + trackHeight;
            gr.DrawImage(timeBmp, 0, offset); //draw in the top time scale

            compositeBmp.Save(Path.Combine(outputDirectory.FullName, opFileStem + ".png"));
        }
Example #14
0
        public static void Execute(Arguments arguments)
        {
            var inputDirs = arguments.InputDataDirectories.Select(FileInfoExtensions.ToDirectoryInfo);
            var output    = arguments.OutputDirectory.ToDirectoryInfo();

            string date = "# DATE AND TIME: " + DateTime.Now;

            LoggedConsole.WriteLine("\n# DRAW an EASY IMAGE from consecutive days of SUMMARY INDICES in CSV files.");
            LoggedConsole.WriteLine("#    IT IS ASSUMED THAT THE CSV files are already concatenated into 24 hour files.");
            LoggedConsole.WriteLine(date);
            LoggedConsole.WriteLine("# Summary Index.csv files are in directories:");
            foreach (DirectoryInfo dir in inputDirs)
            {
                LoggedConsole.WriteLine("    {0}", dir.FullName);
            }

            LoggedConsole.WriteLine("# Output directory: " + output);
            if (arguments.StartDate == null)
            {
                LoggedConsole.WriteLine("# Start date = NULL (No argument provided). Will revise start date ....");
            }
            else
            {
                LoggedConsole.WriteLine("# Start date = " + arguments.StartDate.ToString());
            }

            if (arguments.EndDate == null)
            {
                LoggedConsole.WriteLine("# End   date = NULL (No argument provided). Will revise end date ....");
            }
            else
            {
                LoggedConsole.WriteLine("# End   date = " + arguments.EndDate.ToString());
            }

            LoggedConsole.WriteLine("# FILE FILTER = " + arguments.FileFilter);
            LoggedConsole.WriteLine();

            // PATTERN SEARCH FOR SUMMARY INDEX FILES.
            //string pattern = "*__Towsey.Acoustic.Indices.csv";
            FileInfo[] csvFiles = IndexMatrices.GetFilesInDirectories(inputDirs.ToArray(), arguments.FileFilter);

            //LoggedConsole.WriteLine("# Subdirectories Count = " + subDirectories.Length);
            LoggedConsole.WriteLine("# SummaryIndexFiles.csv Count = " + csvFiles.Length);

            if (csvFiles.Length == 0)
            {
                LoggedConsole.WriteErrorLine("\n\nWARNING from method DrawEasyImage.Execute():");
                LoggedConsole.WriteErrorLine("        No SUMMARY index files were found.");
                LoggedConsole.WriteErrorLine("        RETURNING EMPTY HANDED!");
                return;
            }

            // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo>
            //var sortedDictionaryOfDatesAndFiles = LDSpectrogramStitching.FilterFilesForDates(csvFiles, arguments.TimeSpanOffsetHint);

            // calculate new start date if passed value = null.
            DateTimeOffset?startDate = arguments.StartDate;
            DateTimeOffset?endDate   = arguments.EndDate;

            TimeSpan totalTimespan = (DateTimeOffset)endDate - (DateTimeOffset)startDate;
            int      dayCount      = totalTimespan.Days + 1; // assume last day has full 24 hours of recording available.

            LoggedConsole.WriteLine("\n# Start date = " + startDate.ToString());
            LoggedConsole.WriteLine("# End   date = " + endDate.ToString());
            LoggedConsole.WriteLine(string.Format("# Elapsed time = {0:f1} hours", dayCount * 24));
            LoggedConsole.WriteLine("# Day  count = " + dayCount + " (inclusive of start and end days)");
            LoggedConsole.WriteLine("# Time Zone  = " + arguments.TimeSpanOffsetHint.ToString());

            // create top level output directory if it does not exist.
            DirectoryInfo opDir = output;

            if (!opDir.Exists)
            {
                opDir.Create();
            }

            // SET UP DEFAULT SITE LOCATION INFO    --  DISCUSS IWTH ANTHONY
            // The following location data is used only to draw the sunrise/sunset tracks on images.
            double?latitude        = null;
            double?longitude       = null;
            var    siteDescription = new SiteDescription();

            siteDescription.SiteName  = arguments.FileStemName;
            siteDescription.Latitude  = latitude;
            siteDescription.Longitude = longitude;

            // the following required if drawing the index images
            FileInfo indexPropertiesConfig = null;

            // require IndexGenerationData and indexPropertiesConfig for drawing
            //indexGenerationData = IndexGenerationData.GetIndexGenerationData(csvFiles[0].Directory);
            indexPropertiesConfig = arguments.IndexPropertiesConfig.ToFileInfo();
            Dictionary <string, IndexProperties>    listOfIndexProperties = IndexProperties.GetIndexProperties(indexPropertiesConfig);
            Tuple <List <string>, List <double[]> > tuple = CsvTools.ReadCSVFile(csvFiles[0].FullName);
            var names = tuple.Item1;

            // default EASY indices
            int    redID = 3; // backgroundNoise
            int    grnID = 5; // avSNROfActiveframes
            int    bluID = 7; // events per second
            string rep   = @"bgn-avsnr-evn";

            // ACI Ht Hpeaks EASY indices
            if (false)
            {
                redID = 11;  // ACI
                grnID = 12;  // Ht

                //bluID = 13;  // HavgSp
                //bluID = 14;  // Hvariance
                //bluID = 15;  // Hpeaks
                bluID = 16;  // Hcov

                //bluID = 7;  // SPT
                rep = @"aci-ht-hcov";

                //rep = @"aci-ht-spt";
            }

            // LF, MF, HF
            if (true)
            {
                redID = 10;  // LF
                grnID = 9;   // MF
                bluID = 8;   // HF
                rep   = @"lf-mf-hf";
            }

            IndexProperties redIndexProps = listOfIndexProperties[names[redID]];
            IndexProperties grnIndexProps = listOfIndexProperties[names[grnID]];
            IndexProperties bluIndexProps = listOfIndexProperties[names[bluID]];

            int dayPixelHeight = 4;
            int rowCount       = (dayPixelHeight * dayCount) + 35; // +30 for grid lines
            int colCount       = 1440;
            var bitmap         = new Image <Rgb24>(colCount, rowCount);
            var colour         = Color.Yellow;
            int currentRow     = 0;
            var oneDay         = TimeSpan.FromHours(24);
            int graphWidth     = colCount;
            int trackHeight    = 20;
            var stringFont     = Drawing.Arial8;

            string[] monthNames = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

            // for drawing the y-axis scale
            int scaleWidth = trackHeight + 7;
            var yAxisScale = new Image <Rgb24>(scaleWidth, rowCount + (2 * trackHeight));

            yAxisScale.Mutate(g =>
            {
                g.Clear(Color.Black);

                // loop over days
                for (int d = 0; d < dayCount; d++)
                {
                    var thisday = ((DateTimeOffset)startDate).AddDays(d);

                    if (thisday.Day == 1)
                    {
                        int nextRow = currentRow + 1;
                        for (int c = 0; c < colCount; c++)
                        {
                            bitmap[c, currentRow] = Color.Gray;
                            bitmap[c, nextRow]    = Color.Gray;
                        }

                        for (int c = 0; c < scaleWidth; c++)
                        {
                            yAxisScale[c, currentRow + trackHeight] = Color.Gray;
                            yAxisScale[c, nextRow + trackHeight]    = Color.Gray;
                        }

                        string month = monthNames[thisday.Month - 1];
                        if (thisday.Month == 1) // January
                        {
                            g.DrawText(thisday.Year.ToString(), stringFont, Color.White,
                                       new PointF(0, nextRow + trackHeight + 1));  //draw time
                            g.DrawText(month, stringFont, Color.White,
                                       new PointF(1, nextRow + trackHeight + 11)); //draw time
                        }
                        else
                        {
                            g.DrawText(month, stringFont, Color.White,
                                       new PointF(1, nextRow + trackHeight + 1)); //draw time
                        }

                        currentRow += 2;
                    }

                    // get the exact date and time
                    LoggedConsole.WriteLine($"READING DAY {d + 1} of {dayCount}:   {thisday.ToString()}");

                    // CREATE DAY LEVEL OUTPUT DIRECTORY for this day
                    string dateString = $"{thisday.Year}{thisday.Month:D2}{thisday.Day:D2}";

                    tuple      = CsvTools.ReadCSVFile(csvFiles[d].FullName);
                    var arrays = tuple.Item2;

                    var redArray = arrays[redID];
                    var grnArray = arrays[grnID];
                    var bluArray = arrays[bluID];

                    // NormaliseMatrixValues the indices
                    redArray = DataTools.NormaliseInZeroOne(redArray, redIndexProps.NormMin, redIndexProps.NormMax);
                    grnArray = DataTools.NormaliseInZeroOne(grnArray, grnIndexProps.NormMin, grnIndexProps.NormMax);
                    bluArray = DataTools.NormaliseInZeroOne(bluArray, bluIndexProps.NormMin, bluIndexProps.NormMax);

                    for (int c = 0; c < colCount; c++)
                    {
                        for (int r = 0; r < dayPixelHeight; r++)
                        {
                            //transformedValue = Math.Sqrt(redArray[c]);
                            var transformedValue = redArray[c] * redArray[c];
                            int redVal           = (int)Math.Round(transformedValue * 255);
                            if (redVal < 0)
                            {
                                redVal = 0;
                            }
                            else if (redVal > 255)
                            {
                                redVal = 255;
                            }

                            //transformedValue = Math.Sqrt(grnArray[c]);
                            transformedValue = grnArray[c] * grnArray[c]; // square the value
                            int grnVal       = (int)Math.Round(transformedValue * 255);
                            if (grnVal < 0)
                            {
                                grnVal = 0;
                            }
                            else if (grnVal > 255)
                            {
                                grnVal = 255;
                            }

                            //transformedValue = Math.Sqrt(bluArray[c]);
                            transformedValue = bluArray[c] * bluArray[c]; // square the value
                            int bluVal       = (int)Math.Round(transformedValue * 255);
                            if (bluVal < 0)
                            {
                                bluVal = 0;
                            }
                            else if (bluVal > 255)
                            {
                                bluVal = 255;
                            }

                            bitmap[c, currentRow + r] = Color.FromRgb((byte)redVal, (byte)grnVal, (byte)bluVal);
                        }
                    } // over all columns

                    currentRow += dayPixelHeight;

                    if (thisday.Day % 7 == 0)
                    {
                        for (int c = 0; c < colCount; c++)
                        {
                            bitmap[c, currentRow] = Color.Gray;
                        }

                        currentRow++;
                    }
                } // over days
            });
            // draw on civil dawn and dusk lines
            int startdayOfYear = ((DateTimeOffset)startDate).DayOfYear;
            int endDayOfYear   = ((DateTimeOffset)endDate).DayOfYear;

            SunAndMoon.AddSunRiseSetLinesToImage(bitmap, arguments.BrisbaneSunriseDatafile.ToFileInfo(), startdayOfYear, endDayOfYear, dayPixelHeight);

            // add the time scales
            Image <Rgb24> timeBmp1      = ImageTrack.DrawTimeRelativeTrack(oneDay, graphWidth, trackHeight);
            var           imageList     = new [] { timeBmp1, bitmap, timeBmp1 };
            Image <Rgb24> compositeBmp1 = (Image <Rgb24>)ImageTools.CombineImagesVertically(imageList);

            imageList = new [] { yAxisScale, compositeBmp1 };
            Image <Rgb24> compositeBmp2 = (Image <Rgb24>)ImageTools.CombineImagesInLine(imageList);

            // indices used for image
            string        indicesDescription = $"{redIndexProps.Name}|{grnIndexProps.Name}|{bluIndexProps.Name}";
            string        startString        = $"{startDate.Value.Year}/{startDate.Value.Month}/{startDate.Value.Day}";
            string        endString          = $"{endDate.Value.Year}/{endDate.Value.Month}/{endDate.Value.Day}";
            string        title    = $"EASY:   {arguments.FileStemName}    From {startString} to {endString}                          Indices: {indicesDescription}";
            Image <Rgb24> titleBar = ImageTrack.DrawTitleTrack(compositeBmp2.Width, trackHeight, title);

            imageList     = new [] { titleBar, compositeBmp2 };
            compositeBmp2 = (Image <Rgb24>)ImageTools.CombineImagesVertically(imageList);
            var outputFileName = Path.Combine(opDir.FullName, arguments.FileStemName + "." + rep + ".EASY.png");

            compositeBmp2.Save(outputFileName);
        } // Execute()
        public override AnalysisResult2 Analyze <T>(AnalysisSettings analysisSettings, SegmentSettings <T> segmentSettings)
        {
            var configuration = (StandardizedFeatureExtractionConfig)analysisSettings.Configuration;
            var audioFile     = segmentSettings.SegmentAudioFile;
            var recording     = new AudioRecording(audioFile.FullName);

            // Configurations non-specific for bands
            TimeSpan indexCalculationDuration = configuration.IndexCalculationDurationTimeSpan;
            TimeSpan bgNoiseNeighbourhood     = configuration.BgNoiseBuffer;

            // Bands
            List <StandardizedFeatureExtractionConfig.BandsProperties> bandsList = configuration.Bands;

            // Check if there are identical bands
            CheckForIdenticalBands(bandsList);

            // Estimate total number of subsegments
            double segmentDurationSeconds = segmentSettings.AnalysisIdealSegmentDuration.TotalSeconds;
            double subsegmentDuration     = indexCalculationDuration.TotalSeconds;
            int    subsegmentCount        = (int)Math.Round(segmentDurationSeconds / subsegmentDuration);
            int    totalSubsegmentCount   = subsegmentCount * bandsList.Count;

            // Store results of all subsegments
            var analysisResults = new AnalysisResult2(analysisSettings, segmentSettings, recording.Duration);

            analysisResults.AnalysisIdentifier = this.Identifier;

            var trackScores = new List <Plot>(totalSubsegmentCount);
            var tracks      = new List <SpectralTrack>(totalSubsegmentCount);

            analysisResults.SummaryIndices  = new SummaryIndexBase[totalSubsegmentCount];
            analysisResults.SpectralIndices = new SpectralIndexBase[totalSubsegmentCount];

            // Create list to store images, one for each band. They are later combined into one image.
            var    list          = new List <Image <Rgb24> >();
            string imagePath     = segmentSettings.SegmentImageFile.FullName;
            int    maxImageWidth = 0;

            int bandCount = 0;

            foreach (var band in bandsList)
            {
                Log.DebugFormat("Starting band {0}/{1}", bandCount + 1, bandsList.Count);

                // Calculate spectral indices

                // get a fresh copy of the ICC config
                var config = (IndexCalculateConfig)((ICloneable)configuration).Clone();

                // Add values specific for band from custom configuration file to config
                config.MinBandWidth = band.Bandwidth.Min;
                config.MaxBandWidth = band.Bandwidth.Max;
                config.FrameLength  = band.FftWindow;
                if (band.MelScale != 0)
                {
                    config.FrequencyScale = FreqScaleType.Mel;
                    config.MelScale       = band.MelScale;
                }
                else
                {
                    config.FrequencyScale = FreqScaleType.Linear;
                }

                // Calculate indices for each subsegment and for each band
                IndexCalculateResult[] subsegmentResults = AcousticIndices.CalculateIndicesInSubsegments(
                    recording,
                    segmentSettings.SegmentStartOffset,
                    segmentSettings.AnalysisIdealSegmentDuration,
                    indexCalculationDuration,
                    config.IndexProperties,
                    segmentSettings.Segment.SourceMetadata.SampleRate,
                    config);

                int columnsAmplitudeSpectrogram = subsegmentResults[0].AmplitudeSpectrogram.GetLength(1);
                double[,] amplitudeSpectrogramSegment = new double[0, columnsAmplitudeSpectrogram];

                for (int i = 0; i < subsegmentResults.Length; i++)
                {
                    var indexCalculateResult = subsegmentResults[i];

                    indexCalculateResult.SummaryIndexValues.FileName  = segmentSettings.Segment.SourceMetadata.Identifier;
                    indexCalculateResult.SpectralIndexValues.FileName = segmentSettings.Segment.SourceMetadata.Identifier;

                    analysisResults.SummaryIndices[bandCount + (i * bandsList.Count)]  = indexCalculateResult.SummaryIndexValues;
                    analysisResults.SpectralIndices[bandCount + (i * bandsList.Count)] = indexCalculateResult.SpectralIndexValues;

                    trackScores.AddRange(indexCalculateResult.TrackScores);
                    if (indexCalculateResult.Tracks != null)
                    {
                        tracks.AddRange(indexCalculateResult.Tracks);
                    }

                    if (analysisSettings.AnalysisImageSaveBehavior.ShouldSave())
                    {
                        // Add amplitude spectrograms of each subsegment together to get amplitude spectrogram of one segment
                        double[,] amplitudeSpectrogramSubsegment = indexCalculateResult.AmplitudeSpectrogram;
                        amplitudeSpectrogramSegment = MatrixTools.ConcatenateMatrixRows(
                            amplitudeSpectrogramSegment,
                            amplitudeSpectrogramSubsegment);
                    }
                }

                if (analysisSettings.AnalysisImageSaveBehavior.ShouldSave())
                {
                    // Create image of amplitude spectrogram
                    var image = ImageTools.DrawReversedMatrix(MatrixTools.MatrixRotate90Anticlockwise(amplitudeSpectrogramSegment));

                    // Label information
                    string minBandWidth = band.Bandwidth.Min.ToString();
                    string maxBandWidth = band.Bandwidth.Max.ToString();
                    string fftWindow    = band.FftWindow.ToString();
                    string mel;
                    string melScale;
                    if (band.MelScale != 0)
                    {
                        mel      = "Mel";
                        melScale = band.MelScale.ToString();
                    }
                    else
                    {
                        mel      = "Standard";
                        melScale = 0.ToString();
                    }

                    // Create label
                    string   segmentSeparator = "_";
                    string[] segments         = { minBandWidth, maxBandWidth, fftWindow, mel, melScale };
                    string   labelText        = segments.Aggregate(string.Empty, (aggregate, item) => aggregate + segmentSeparator + item);

                    var stringFont = Drawing.Arial14;
                    int width      = 250;
                    int height     = image.Height;
                    var label      = new Image <Rgb24>(width, height);
                    label.Mutate(g1 =>
                    {
                        g1.Clear(Color.Gray);
                        g1.DrawText(labelText, stringFont, Color.Black, new PointF(4, 30));
                        g1.DrawLine(new Pen(Color.Black, 1), 0, 0, width, 0); //draw upper boundary
                        g1.DrawLine(new Pen(Color.Black, 1), 0, 1, width, 1); //draw upper boundary
                    });

                    var labelledImage = ImageTools.CombineImagesInLine(label, image);

                    // Add labeled image to list
                    list.Add(labelledImage);

                    // Update maximal width of image
                    if (image.Width > maxImageWidth)
                    {
                        maxImageWidth = image.Width;
                    }
                }

                bandCount += 1;
                Log.InfoFormat("Completed band {0}/{1}", bandCount, bandsList.Count);
            }

            if (analysisSettings.AnalysisDataSaveBehavior)
            {
                this.WriteSummaryIndicesFile(segmentSettings.SegmentSummaryIndicesFile, analysisResults.SummaryIndices);
                analysisResults.SummaryIndicesFile  = segmentSettings.SegmentSummaryIndicesFile;
                analysisResults.SpectraIndicesFiles =
                    this.WriteSpectrumIndicesFiles(
                        segmentSettings.SegmentSpectrumIndicesDirectory,
                        Path.GetFileNameWithoutExtension(segmentSettings.SegmentAudioFile.Name),
                        analysisResults.SpectralIndices);
            }

            if (analysisSettings.AnalysisImageSaveBehavior.ShouldSave())
            {
                var finalImage = ImageTools.CombineImagesVertically(list, maxImageWidth);
                finalImage.Save(imagePath);
                analysisResults.ImageFile = new FileInfo(imagePath);
                LoggedConsole.WriteLine("See {0} for spectrogram pictures", imagePath);
            }

            return(analysisResults);
        }
        /// <summary>
        /// Generates the FREQUENCY x OSCILLATIONS Graphs and csv
        /// </summary>
        public static Tuple <Image, double[, ], double[]> GenerateOscillationDataAndImages(FileInfo audioSegment, Dictionary <string, string> configDict, bool drawImage = false)
        {
            // set two oscillation detection parameters
            double sensitivity = DefaultSensitivityThreshold;

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SensitivityThreshold))
            {
                sensitivity = double.Parse(configDict[AnalysisKeys.OscilDetection2014SensitivityThreshold]);
            }

            // Sample length i.e. number of frames spanned to calculate oscillations per second
            int sampleLength = DefaultSampleLength;

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SampleLength))
            {
                sampleLength = int.Parse(configDict[AnalysisKeys.OscilDetection2014SampleLength]);
            }

            SonogramConfig sonoConfig = new SonogramConfig(configDict); // default values config

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014FrameSize))
            {
                sonoConfig.WindowSize = int.Parse(configDict[AnalysisKeys.OscilDetection2014FrameSize]);
            }

            var          recordingSegment = new AudioRecording(audioSegment.FullName);
            BaseSonogram sonogram         = new AmplitudeSonogram(sonoConfig, recordingSegment.WavReader);

            // remove the DC bin if it has not already been removed.
            // Assume test of divisible by 2 is good enough.
            int binCount = sonogram.Data.GetLength(1);

            if (!binCount.IsEven())
            {
                sonogram.Data = MatrixTools.Submatrix(sonogram.Data, 0, 1, sonogram.FrameCount - 1, binCount - 1);
            }

            //LoggedConsole.WriteLine("Oscillation Detection: Sample rate     = {0}", sonogram.SampleRate);
            //LoggedConsole.WriteLine("Oscillation Detection: FramesPerSecond = {0}", sonogram.FramesPerSecond);

            // Do LOCAL CONRAST Normalisation first. LCN over frequency bins is better and faster than standard noise removal.
            double neighbourhoodSeconds = 0.25;
            int    neighbourhoodFrames  = (int)(sonogram.FramesPerSecond * neighbourhoodSeconds);
            double lcnContrastLevel     = 0.5; // was previously 0.1

            LoggedConsole.WriteLine("LCN: FramesPerSecond (Prior to LCN) = {0}", sonogram.FramesPerSecond);
            LoggedConsole.WriteLine("LCN: Neighbourhood of {0} seconds = {1} frames", neighbourhoodSeconds, neighbourhoodFrames);
            sonogram.Data = NoiseRemoval_Briggs.NoiseReduction_byLCNDivision(sonogram.Data, neighbourhoodFrames, lcnContrastLevel);

            string algorithmName1 = "autocorr-svd-fft";

            double[,] freqOscilMatrix1 = GetFrequencyByOscillationsMatrix(sonogram.Data, sensitivity, sampleLength, algorithmName1);

            //get the max spectral index - this reduces the matrix to an array
            double[] spectralIndex1 = ConvertMatrix2SpectralIndexBySummingFreqColumns(freqOscilMatrix1, 0);

            Image compositeImage = null;

            if (drawImage)
            {
                string algorithmName2 = "autocorr-fft";
                double[,] freqOscilMatrix2 = GetFrequencyByOscillationsMatrix(sonogram.Data, sensitivity, sampleLength, algorithmName2);
                var image1 = GetFreqVsOscillationsImage(freqOscilMatrix1, sonogram.FramesPerSecond, sonogram.FBinWidth, sampleLength, algorithmName1);
                var image2 = GetFreqVsOscillationsImage(freqOscilMatrix2, sonogram.FramesPerSecond, sonogram.FBinWidth, sampleLength, algorithmName2);
                compositeImage = ImageTools.CombineImagesInLine(new[] { image1, image2 });
            }

            // Return (1) composite image of oscillations, (2) data matrix from only one algorithm,
            //     and (3) spectrum of oscillation values for accumulation into data from a multi-hour recording.
            return(Tuple.Create(compositeImage, freqOscilMatrix1, spectralIndex1));
        }
Example #17
0
        public static void Main(Arguments arguments)
        {
            // 1. set up the necessary files
            FileInfo      sourceRecording = arguments.Source;
            FileInfo      configFile      = arguments.Config.ToFileInfo();
            DirectoryInfo opDir           = arguments.Output;

            opDir.Create();

            if (arguments.StartOffset.HasValue ^ arguments.EndOffset.HasValue)
            {
                throw new InvalidStartOrEndException("If StartOffset or EndOffset is specified, then both must be specified");
            }

            var offsetsProvided = arguments.StartOffset.HasValue && arguments.EndOffset.HasValue;

            // set default offsets - only use defaults if not provided in argments list
            TimeSpan?startOffset = null;
            TimeSpan?endOffset   = null;

            if (offsetsProvided)
            {
                startOffset = TimeSpan.FromSeconds(arguments.StartOffset.Value);
                endOffset   = TimeSpan.FromSeconds(arguments.EndOffset.Value);
            }

            const string Title = "# MAKE A SONOGRAM FROM AUDIO RECORDING and do OscillationsGeneric activity.";
            string       date  = "# DATE AND TIME: " + DateTime.Now;

            LoggedConsole.WriteLine(Title);
            LoggedConsole.WriteLine(date);
            LoggedConsole.WriteLine("# Input  audio file: " + sourceRecording.Name);

            string sourceName = Path.GetFileNameWithoutExtension(sourceRecording.FullName);

            // 2. get the config dictionary
            Config configuration = ConfigFile.Deserialize(configFile);

            // below three lines are examples of retrieving info from Config config
            // string analysisIdentifier = configuration[AnalysisKeys.AnalysisName];
            // bool saveIntermediateWavFiles = (bool?)configuration[AnalysisKeys.SaveIntermediateWavFiles] ?? false;
            // scoreThreshold = (double?)configuration[AnalysisKeys.EventThreshold] ?? scoreThreshold;

            // Resample rate must be 2 X the desired Nyquist. Default is that of recording.
            var resampleRate = configuration.GetIntOrNull(AnalysisKeys.ResampleRate) ?? AppConfigHelper.DefaultTargetSampleRate;

            var configDict = new Dictionary <string, string>(configuration.ToDictionary());

            // #NOISE REDUCTION PARAMETERS
            //string noisereduce = configDict[ConfigKeys.Mfcc.Key_NoiseReductionType];
            configDict[AnalysisKeys.NoiseDoReduction]   = "false";
            configDict[AnalysisKeys.NoiseReductionType] = "NONE";

            configDict[AnalysisKeys.AddAxes] = configuration[AnalysisKeys.AddAxes] ?? "true";
            configDict[AnalysisKeys.AddSegmentationTrack] = configuration[AnalysisKeys.AddSegmentationTrack] ?? "true";

            configDict[ConfigKeys.Recording.Key_RecordingCallName] = sourceRecording.FullName;
            configDict[ConfigKeys.Recording.Key_RecordingFileName] = sourceRecording.Name;

            configDict[AnalysisKeys.AddTimeScale]         = configuration[AnalysisKeys.AddTimeScale] ?? "true";
            configDict[AnalysisKeys.AddAxes]              = configuration[AnalysisKeys.AddAxes] ?? "true";
            configDict[AnalysisKeys.AddSegmentationTrack] = configuration[AnalysisKeys.AddSegmentationTrack] ?? "true";

            // ####################################################################

            // print out the sonogram parameters
            LoggedConsole.WriteLine("\nPARAMETERS");
            foreach (KeyValuePair <string, string> kvp in configDict)
            {
                LoggedConsole.WriteLine("{0}  =  {1}", kvp.Key, kvp.Value);
            }

            LoggedConsole.WriteLine("Sample Length for detecting oscillations = {0}", SampleLength);

            // 3: GET RECORDING
            FileInfo tempAudioSegment = new FileInfo(Path.Combine(opDir.FullName, "tempWavFile.wav"));

            // delete the temp audio file if it already exists.
            if (File.Exists(tempAudioSegment.FullName))
            {
                File.Delete(tempAudioSegment.FullName);
            }

            // This line creates a temporary version of the source file downsampled as per entry in the config file
            MasterAudioUtility.SegmentToWav(sourceRecording, tempAudioSegment, new AudioUtilityRequest()
            {
                TargetSampleRate = resampleRate
            });

            // 1) get amplitude spectrogram
            AudioRecording recordingSegment = new AudioRecording(tempAudioSegment.FullName);
            SonogramConfig sonoConfig       = new SonogramConfig(configDict); // default values config
            BaseSonogram   sonogram         = new AmplitudeSonogram(sonoConfig, recordingSegment.WavReader);

            Console.WriteLine("FramesPerSecond = {0}", sonogram.FramesPerSecond);

            // remove the DC bin
            sonogram.Data = MatrixTools.Submatrix(sonogram.Data, 0, 1, sonogram.FrameCount - 1, sonogram.Configuration.FreqBinCount);

            // ###############################################################
            // DO LocalContrastNormalisation
            //int fieldSize = 9;
            //sonogram.Data = LocalContrastNormalisation.ComputeLCN(sonogram.Data, fieldSize);
            // LocalContrastNormalisation over frequency bins is better and faster.
            int    neighbourhood = 15;
            double contrastLevel = 0.5;

            sonogram.Data = NoiseRemoval_Briggs.NoiseReduction_byLCNDivision(sonogram.Data, neighbourhood, contrastLevel);

            // ###############################################################
            // lowering the sensitivity threshold increases the number of hits.
            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SensitivityThreshold))
            {
                Oscillations2014.DefaultSensitivityThreshold = double.Parse(configDict[AnalysisKeys.OscilDetection2014SensitivityThreshold]);
            }

            if (configDict.ContainsKey(AnalysisKeys.OscilDetection2014SampleLength))
            {
                Oscillations2014.DefaultSampleLength = int.Parse(configDict[AnalysisKeys.OscilDetection2014SensitivityThreshold]);
            }

            var list1 = new List <Image>();

            //var result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, 64, "Autocorr-FFT");
            //list1.Add(result.FreqOscillationImage);
            var result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-FFT");

            list1.Add(result.FreqOscillationImage);
            result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-SVD-FFT");
            list1.Add(result.FreqOscillationImage);
            result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-WPD");
            list1.Add(result.FreqOscillationImage);
            Image compositeOscImage1 = ImageTools.CombineImagesInLine(list1.ToArray());

            // ###############################################################

            // init the sonogram image stack
            var sonogramList = new List <Image>();
            var image        = sonogram.GetImageFullyAnnotated("AMPLITUDE SPECTROGRAM");

            sonogramList.Add(image);

            //string testPath = @"C:\SensorNetworks\Output\Sonograms\amplitudeSonogram.png";
            //image.Save(testPath, ImageFormat.Png);

            Image envelopeImage = ImageTrack.DrawWaveEnvelopeTrack(recordingSegment, image.Width);

            sonogramList.Add(envelopeImage);

            // 2) now draw the standard decibel spectrogram
            sonogram = new SpectrogramStandard(sonoConfig, recordingSegment.WavReader);

            // ###############################################################
            list1 = new List <Image>();

            //result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, 64, "Autocorr-FFT");
            //list1.Add(result.FreqOscillationImage);
            result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-FFT");
            list1.Add(result.FreqOscillationImage);
            result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-SVD-FFT");
            list1.Add(result.FreqOscillationImage);
            result = Oscillations2014.GetFreqVsOscillationsDataAndImage(sonogram, "Autocorr-WPD");
            list1.Add(result.FreqOscillationImage);
            Image compositeOscImage2 = ImageTools.CombineImagesInLine(list1.ToArray());

            // ###############################################################
            //image = sonogram.GetImageFullyAnnotated("DECIBEL SPECTROGRAM");
            //list.Add(image);

            // combine eight images
            list1 = new List <Image>();
            list1.Add(compositeOscImage1);
            list1.Add(compositeOscImage2);
            Image  compositeOscImage3 = ImageTools.CombineImagesVertically(list1.ToArray());
            string imagePath3         = Path.Combine(opDir.FullName, sourceName + "_freqOscilMatrix.png");

            compositeOscImage3.Save(imagePath3, ImageFormat.Png);

            Image segmentationImage = ImageTrack.DrawSegmentationTrack(
                sonogram,
                EndpointDetectionConfiguration.K1Threshold,
                EndpointDetectionConfiguration.K2Threshold,
                image.Width);

            sonogramList.Add(segmentationImage);

            // 3) now draw the noise reduced decibel spectrogram
            sonoConfig.NoiseReductionType      = NoiseReductionType.Standard;
            sonoConfig.NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 3.0;

            sonogram = new SpectrogramStandard(sonoConfig, recordingSegment.WavReader);
            image    = sonogram.GetImageFullyAnnotated("NOISE-REDUCED DECIBEL  SPECTROGRAM");
            sonogramList.Add(image);

            // ###############################################################
            // deriving osscilation graph from this noise reduced spectrogram did not work well
            //Oscillations2014.SaveFreqVsOscillationsDataAndImage(sonogram, sampleLength, algorithmName, opDir);
            // ###############################################################

            Image  compositeSonogram = ImageTools.CombineImagesVertically(sonogramList);
            string imagePath2        = Path.Combine(opDir.FullName, sourceName + ".png");

            compositeSonogram.Save(imagePath2, ImageFormat.Png);

            LoggedConsole.WriteLine("\n##### FINISHED FILE ###################################################\n");
        }
Example #18
0
        } // method DrawAggregatedSpectrograms()

        public static Image DrawGrayScaleSpectrograms(Arguments arguments, string fileStem, TimeSpan dataScale, Dictionary <string, double[, ]> spectra = null)
        {
            int sampleRate = 22050;
            int frameWidth = 512;

            //double backgroundFilter = 0.0; // 0.0 means small values are removed.
            double backgroundFilter = 0.75;  // 0.75 means small values are accentuated.
            string analysisType     = AcousticIndices.TowseyAcoustic;

            string[] keys = LDSpectrogramRGB.GetArrayOfAvailableKeys();

            //LoggedConsole.WriteLine("# Spectrogram Config      file: " + arguments.SpectrogramConfigPath);
            //LoggedConsole.WriteLine("# Index Properties Config file: " + arguments.IndexPropertiesConfig);
            var inputDirectory = arguments.InputDataDirectory;
            Dictionary <string, IndexProperties> indexProperties = IndexProperties.GetIndexProperties(arguments.IndexPropertiesConfig.ToFileInfo());

            if (spectra == null)
            {
                //C:\SensorNetworks\Output\BIRD50\Training\ID0001\Towsey.Acoustic\ID0001__Towsey.Acoustic.ACI
                spectra = IndexMatrices.ReadSpectralIndices(inputDirectory.ToDirectoryInfo(), fileStem, analysisType, keys);
            }

            // note: the spectra are oriented as per visual orientation, i.e. xAxis = time frames
            //int frameCount = spectra[keys[0]].GetLength(1);
            var cs1 = new LDSpectrogramRGB(minuteOffset: TimeSpan.Zero, xScale: dataScale, sampleRate: sampleRate, frameWidth: frameWidth, colourMap: null)
            {
                FileName                 = fileStem,
                BackgroundFilter         = backgroundFilter,
                IndexCalculationDuration = dataScale,
            };

            cs1.SetSpectralIndexProperties(indexProperties); // set the relevant dictionary of index properties
            cs1.SpectrogramMatrices = spectra;
            if (cs1.GetCountOfSpectrogramMatrices() == 0)
            {
                LoggedConsole.WriteLine("WARNING:  " + fileStem + ":   No spectrogram matrices in the dictionary. Spectrogram files do not exist?");
                return(null);
            }

            List <Image> list       = new List <Image>();
            Font         stringFont = new Font("Arial", 14);

            foreach (string key in keys)
            {
                var image = cs1.DrawGreyscaleSpectrogramOfIndex(key);

                int width  = 70;
                int height = image.Height;
                var label  = new Bitmap(width, height);
                var g1     = Graphics.FromImage(label);
                g1.Clear(Color.Gray);
                g1.DrawString(key, stringFont, Brushes.Black, new PointF(4, 30));
                g1.DrawLine(new Pen(Color.Black), 0, 0, width, 0); //draw upper boundary
                g1.DrawLine(new Pen(Color.Black), 0, 1, width, 1); //draw upper boundary

                Image[] imagearray    = { label, image };
                var     labelledImage = ImageTools.CombineImagesInLine(imagearray);
                list.Add(labelledImage);
            } //foreach key

            var combinedImage = ImageTools.CombineImagesVertically(list.ToArray());

            return(combinedImage);
        } // method DrawGrayScaleSpectrograms()
Example #19
0
        } // method DrawAggregatedSpectrograms()

        public static Image <Rgb24> DrawGrayScaleSpectrograms(Arguments arguments, string fileStem, TimeSpan dataScale, Dictionary <string, double[, ]> spectra = null)
        {
            // default values
            int sampleRate = 22050;
            int frameWidth = 512;

            //double backgroundFilter = 0.0; // 0.0 means small values are removed.
            double backgroundFilter = 0.75;  // 0.75 means small values are accentuated.
            string analysisType     = AcousticIndices.TowseyAcoustic;

            string[] keys           = LDSpectrogramRGB.GetArrayOfAvailableKeys();
            var      inputDirectory = arguments.InputDataDirectory;
            Dictionary <string, IndexProperties> indexProperties = IndexProperties.GetIndexProperties(arguments.IndexPropertiesConfig.ToFileInfo());

            if (spectra == null)
            {
                spectra = IndexMatrices.ReadSpectralIndices(inputDirectory.ToDirectoryInfo(), fileStem, analysisType, keys);
            }

            // note: the spectra are oriented as per visual orientation, i.e. xAxis = time frames
            //int frameCount = spectra[keys[0]].GetLength(1);
            var cs1 = new LDSpectrogramRGB(minuteOffset: TimeSpan.Zero, xScale: dataScale, sampleRate: sampleRate, frameWidth: frameWidth, colorMap: null)
            {
                FileName                 = fileStem,
                BackgroundFilter         = backgroundFilter,
                IndexCalculationDuration = dataScale,
            };

            cs1.SetSpectralIndexProperties(indexProperties); // set the relevant dictionary of index properties
            cs1.SpectrogramMatrices = spectra;
            if (cs1.GetCountOfSpectrogramMatrices() == 0)
            {
                LoggedConsole.WriteLine("WARNING:  " + fileStem + ":   No spectrogram matrices in the dictionary. Spectrogram files do not exist?");
                return(null);
            }

            var list       = new List <Image <Rgb24> >();
            var stringFont = Drawing.Arial14;

            foreach (string key in keys)
            {
                var image = cs1.DrawGreyscaleSpectrogramOfIndex(key);

                int width  = 70;
                int height = image.Height;
                var label  = new Image <Rgb24>(width, height);
                label.Mutate(g1 =>
                {
                    g1.Clear(Color.Gray);
                    g1.DrawText(key, stringFont, Color.Black, new PointF(4, 30));
                    g1.DrawLine(new Pen(Color.Black, 1), 0, 0, width, 0); //draw upper boundary
                    g1.DrawLine(new Pen(Color.Black, 1), 0, 1, width, 1); //draw upper boundary
                });
                var imagearray    = new[] { label, image };
                var labelledImage = ImageTools.CombineImagesInLine(imagearray);
                list.Add(labelledImage);
            } //foreach key

            var combinedImage = ImageTools.CombineImagesVertically(list);

            return(combinedImage);
        } // method DrawGrayScaleSpectrograms()
        public static void ConcatenateDays()
        {
            DirectoryInfo parentDir      = new DirectoryInfo(@"C:\SensorNetworks\Output\Frommolt");
            DirectoryInfo dataDir        = new DirectoryInfo(parentDir + @"\AnalysisOutput\mono");
            var           imageDirectory = new DirectoryInfo(parentDir + @"\ConcatImageOutput");

            //string indexPropertiesConfig = @"C:\Work\GitHub\audio-analysis\AudioAnalysis\AnalysisConfigFiles\IndexPropertiesConfigHiRes.yml";
            DateTimeOffset?startDate          = new DateTimeOffset(2012, 03, 29, 0, 0, 0, TimeSpan.Zero);
            DateTimeOffset?endDate            = new DateTimeOffset(2012, 06, 20, 0, 0, 0, TimeSpan.Zero);
            var            timeSpanOffsetHint = new TimeSpan(01, 0, 0);

            //string fileSuffix = @"2Maps.png";
            //string fileSuffix = @"ACI-ENT-EVN.png";
            // WARNING: POW was removed in December 2018
            string fileSuffix = @"BGN-POW-EVN.png";

            TimeSpan totalTimespan = (DateTimeOffset)endDate - (DateTimeOffset)startDate;
            int      dayCount      = totalTimespan.Days + 1; // assume last day has full 24 hours of recording available.

            bool verbose = true;

            if (verbose)
            {
                LoggedConsole.WriteLine("\n# Start date = " + startDate.ToString());
                LoggedConsole.WriteLine("# End   date = " + endDate.ToString());
                LoggedConsole.WriteLine(string.Format("# Elapsed time = {0:f1} hours", dayCount * 24));
                LoggedConsole.WriteLine("# Day  count = " + dayCount + " (inclusive of start and end days)");
                LoggedConsole.WriteLine("# Time Zone  = " + timeSpanOffsetHint.ToString());
            }

            //string dirMatch = "Monitoring_Rosin_2012*T*+0200_.merged.wav.channel_0.wav";
            string stem     = "Monitoring_Rosin_2012????T??0000+0200_.merged.wav.channel_";
            string dirMatch = stem + "?.wav";

            DirectoryInfo[] subDirectories = dataDir.GetDirectories(dirMatch, SearchOption.AllDirectories);

            string format   = "yyyyMMdd";
            string startDay = ((DateTimeOffset)startDate).ToString(format);

            //string fileMatch = stem + "?__" + fileSuffix;
            //FileInfo[] files = IndexMatrices.GetFilesInDirectories(subDirectories, fileMatch);

            // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo>
            //var sortedDictionaryOfDatesAndFiles = FileDateHelpers.FilterFilesForDates(files, timeSpanOffsetHint);

            //following needed if a day is missing.
            int defaultDayWidth  = 20;
            int defaultDayHeight = 300;

            Brush brush      = Brushes.White;
            Font  stringFont = new Font("Tahoma", 12);

            var list = new List <Image>();

            // loop over days
            for (int d = 0; d < dayCount; d++)
            {
                Console.WriteLine(string.Format("Day {0} of {1} days", d, dayCount));
                var    thisday = ((DateTimeOffset)startDate).AddDays(d);
                string date    = thisday.ToString(format);

                stem = "Monitoring_Rosin_" + date + "T??0000+0200_.merged.wav.channel_";
                string     fileMatch = stem + "?__" + fileSuffix;
                FileInfo[] files     = IndexMatrices.GetFilesInDirectories(subDirectories, fileMatch);
                if (files.Length == 0)
                {
                    Bitmap   gapImage = new Bitmap(defaultDayWidth, defaultDayHeight);
                    Graphics g5       = Graphics.FromImage(gapImage);
                    g5.Clear(Color.Gray);
                    g5.DrawString("Day", stringFont, brush, new PointF(2, 5));
                    g5.DrawString("missing", stringFont, brush, new PointF(2, 35));
                    list.Add(gapImage);

                    continue;
                }

                // Sort the files by date and return as a dictionary: sortedDictionaryOfDatesAndFiles<DateTimeOffset, FileInfo>
                //var sortedDictionaryOfDatesAndFiles = FileDateHelpers.FilterFilesForDates(files, timeSpanOffsetHint);

                Image image = ConcatenateFourChannelImages(files, imageDirectory, fileSuffix, date);

                defaultDayHeight = image.Height;
                list.Add(image);
            }

            Image combinedImage = ImageTools.CombineImagesInLine(list);

            Bitmap   labelImage1 = new Bitmap(combinedImage.Width, 24);
            Graphics g1          = Graphics.FromImage(labelImage1);

            g1.Clear(Color.Black);
            g1.DrawString(fileSuffix, stringFont, brush, new PointF(2, 2));

            //labelImage1.Save(Path.Combine(imageDirectory.FullName, suffix1));

            Graphics g = Graphics.FromImage(combinedImage);

            g.DrawImage(labelImage1, 0, 0);
            string fileName = string.Format(startDay + "." + fileSuffix);

            combinedImage.Save(Path.Combine(imageDirectory.FullName, fileName));
        }