From d0b228de88e53128a9d94479bb7b328791bf399a Mon Sep 17 00:00:00 2001 From: towsey Date: Fri, 16 Aug 2019 17:59:36 +1000 Subject: [PATCH] Write methods to read ribbon images Reading ribbon plots into a csv file to generate a large feature vector spectral indices. Read from ribbon plots because only need 32 values per index. There is no Issue number yet. --- .../Draw/RibbonPlots/RibbonPlot.Entry.cs | 2 +- src/AnalysisPrograms/Sandpit.cs | 3 +- .../AudioAnalysisTools.csproj | 1 + .../LDSpectrogramRGB.cs | 347 ++---------------- .../LdSpectrogramRibbons.cs | 332 +++++++++++++++++ 5 files changed, 362 insertions(+), 323 deletions(-) create mode 100644 src/AudioAnalysisTools/LongDurationSpectrograms/LdSpectrogramRibbons.cs diff --git a/src/AnalysisPrograms/Draw/RibbonPlots/RibbonPlot.Entry.cs b/src/AnalysisPrograms/Draw/RibbonPlots/RibbonPlot.Entry.cs index 708bee5cf..209ab02dd 100644 --- a/src/AnalysisPrograms/Draw/RibbonPlots/RibbonPlot.Entry.cs +++ b/src/AnalysisPrograms/Draw/RibbonPlots/RibbonPlot.Entry.cs @@ -101,7 +101,7 @@ void Add(string colorMap) } // try to find the associated ribbon - var searchPattern = "*" + colorMap + LDSpectrogramRGB.SpectralRibbonTag + "*"; + var searchPattern = "*" + colorMap + LdSpectrogramRibbons.SpectralRibbonTag + "*"; if (Log.IsVerboseEnabled()) { Log.Verbose($"Searching `{indexData.Source?.Directory}` with pattern `{searchPattern}`."); diff --git a/src/AnalysisPrograms/Sandpit.cs b/src/AnalysisPrograms/Sandpit.cs index 9a3038e10..4a1b7f4e8 100644 --- a/src/AnalysisPrograms/Sandpit.cs +++ b/src/AnalysisPrograms/Sandpit.cs @@ -62,7 +62,7 @@ public override Task Execute(CommandLineApplication app) Log.WriteLine("# Start Time = " + tStart.ToString(CultureInfo.InvariantCulture)); //AnalyseFrogDataSet(); - Audio2CsvOverOneFile(); + //Audio2CsvOverOneFile(); //Audio2CsvOverMultipleFiles(); // used to get files from availae for Black rail and Least Bittern papers. @@ -105,6 +105,7 @@ public override Task Execute(CommandLineApplication app) //TestTernaryPlots(); //TestDirectorySearchAndFileSearch(); //TestNoiseReduction(); + LdSpectrogramRibbons.ReadSpectralIndicesFromTwoFalseColourSpectrogramRibbons(); //Oscillations2014.TESTMETHOD_DrawOscillationSpectrogram(); //Oscillations2014.TESTMETHOD_GetSpectralIndex_Osc(); diff --git a/src/AudioAnalysisTools/AudioAnalysisTools.csproj b/src/AudioAnalysisTools/AudioAnalysisTools.csproj index c9707beed..58968ae5d 100644 --- a/src/AudioAnalysisTools/AudioAnalysisTools.csproj +++ b/src/AudioAnalysisTools/AudioAnalysisTools.csproj @@ -281,6 +281,7 @@ + diff --git a/src/AudioAnalysisTools/LongDurationSpectrograms/LDSpectrogramRGB.cs b/src/AudioAnalysisTools/LongDurationSpectrograms/LDSpectrogramRGB.cs index b2d0e2b2f..ba576b454 100644 --- a/src/AudioAnalysisTools/LongDurationSpectrograms/LDSpectrogramRGB.cs +++ b/src/AudioAnalysisTools/LongDurationSpectrograms/LDSpectrogramRGB.cs @@ -64,9 +64,6 @@ namespace AudioAnalysisTools.LongDurationSpectrograms /// public class LDSpectrogramRGB { - public const string SpectralRibbonTag = ".SpectralRibbon"; - public const int RibbonPlotHeight = 32; - // Below is some history about how indices were assigned to the RGB channels to make long-duration false-colour spectrograms // string[] keys = { "ACI", "TEN", "CVR", "BGN", "AVG", "VAR" }; // the OLDEST default i.e. used in 2014 // string[] keys = { "ACI", "ENT", "EVN", "BGN", "POW", "EVN" }; // the OLD default i.e. since July 2015 @@ -570,10 +567,11 @@ public void DrawGreyScaleSpectrograms(DirectoryInfo opdir, string opFileName, st 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); - var indexImage = ImageTools.CombineImagesVertically(new List(new Image[] { header, bmp })); + var indexImage = ImageTools.CombineImagesVertically(new List(new[] { header, bmp })); // save the image - the directory for the path must exist var path = FilenameHelpers.AnalysisResultPath(opdir, opFileName, key, "png"); @@ -763,312 +761,6 @@ public Image DrawDoubleSpectrogram(Image bmp1, Image bmp2, string colorMode) return compositeBmp; } - //############################################################################################################################################################ - //# BELOW METHODS CALCULATE SUMMARY INDEX RIBBONS ############################################################################################################ - - /// - /// Returns an array of summary indices, where each element of the array (one element per minute) is a single summary index - /// derived by averaging the spectral indices for that minute. - /// - public double[] GetSummaryIndexArray(string key) - { - // return matrices have spectrogram orientation - double[,] m = this.GetNormalisedSpectrogramMatrix(key); - int colcount = m.GetLength(1); - double[] indices = new double[colcount]; - - for (int r = 0; r < colcount; r++) - { - indices[r] = MatrixTools.GetColumn(m, r).Average(); - } - - return indices; - } - - public double[] GetSummaryIndicesAveraged() - { - double[] indices1 = this.GetSummaryIndexArray(this.SpectrogramKeys[0]); - double[] indices2 = this.GetSummaryIndexArray(this.SpectrogramKeys[1]); - double[] indices3 = this.GetSummaryIndexArray(this.SpectrogramKeys[2]); - - int count = indices1.Length; - double[] averagedIndices = new double[count]; - for (int r = 0; r < count; r++) - { - averagedIndices[r] = (indices1[r] + indices2[r] + indices3[r]) / 3; - } - - return averagedIndices; - } - - public Image GetSummaryIndexRibbon(string colorMap) - { - string colourKeys = colorMap; - string[] keys = colourKeys.Split('-'); - var indices1 = this.GetSummaryIndexArray(keys[0]); - var indices2 = this.GetSummaryIndexArray(keys[1]); - var indices3 = this.GetSummaryIndexArray(keys[2]); - - int width = indices1.Length; - int height = SpectrogramConstants.HEIGHT_OF_TITLE_BAR; - var image = new Bitmap(width, height); - var g = Graphics.FromImage(image); - for (int i = 0; i < width; i++) - { - Pen pen; - if (double.IsNaN(indices1[i]) || double.IsNaN(indices2[i]) || double.IsNaN(indices3[i])) - { - pen = new Pen(Color.Gray); - } - else - { - int red = (int)(255 * indices1[i]); - int grn = (int)(255 * indices2[i]); - int blu = (int)(255 * indices3[i]); - pen = new Pen(Color.FromArgb(red, grn, blu)); - } - - g.DrawLine(pen, i, 0, i, height); - } - - return image; - } - - /// - /// Returns three arrays of summary indices, for the LOW, MIDDLE and HIGH frequency bands respectively. - /// Each element of an array (one element per minute) is a single summary index - /// derived by averaging the spectral indices in the L, M or H freq band for that minute. - /// - public double[,] GetLoMidHiSummaryIndexArrays(string key) - { - // return matrices have spectrogram orientation - double[,] m = this.GetNormalisedSpectrogramMatrix(key); - int colcount = m.GetLength(1); - double[,] indices = new double[3, colcount]; - - int spectrumLength = m.GetLength(0); - - // here set bounds between the three freq bands - // this is important beacuse it will affect appearance of the final colour ribbon. - int lowBound = (int)(spectrumLength * 0.2); - int midBound = (int)(spectrumLength * 0.5); - int midBandWidth = midBound - lowBound; - int higBandWidth = spectrumLength - midBound; - - // create highband weights - triangular weighting - double[] hibandWeights = new double[higBandWidth]; - double maxweight = 2 / (double)higBandWidth; - double step = maxweight / higBandWidth; - for (int c = 0; c < higBandWidth; c++) - { - hibandWeights[c] = maxweight - (c * step); - } - - for (int c = 0; c < colcount; c++) - { - double[] spectrum = MatrixTools.GetColumn(m, c); - - // reverse the array because low frequencies are at the end of the array. - // because matrices are stored in the orientation that they appear in the final image. - spectrum = DataTools.reverseArray(spectrum); - - // get the low freq band index - indices[0, c] = DataTools.Subarray(spectrum, 0, lowBound).Average(); - - // get the mid freq band index - indices[1, c] = DataTools.Subarray(spectrum, lowBound, midBandWidth).Average(); - - // get the hig freq band index. Here the weights are triangular, sum = 1.0 - double[] subarray = DataTools.Subarray(spectrum, midBound, higBandWidth); - indices[2, c] = DataTools.DotProduct(subarray, hibandWeights); - } - - return indices; - } - - public Image GetSummaryIndexRibbonWeighted(string colorMap) - { - string colourKeys = colorMap; - string[] keys = colourKeys.Split('-'); - - // get the matrices for each of the three indices. - // each matrix has three rows one for each of low, mid and high band averages - // each matrix has one column per minute. - double[,] indices1 = this.GetLoMidHiSummaryIndexArrays(keys[0]); - double[,] indices2 = this.GetLoMidHiSummaryIndexArrays(keys[1]); - double[,] indices3 = this.GetLoMidHiSummaryIndexArrays(keys[2]); - - int width = indices1.GetLength(1); - int height = SpectrogramConstants.HEIGHT_OF_TITLE_BAR; - var image = new Bitmap(width, height); - var g = Graphics.FromImage(image); - - // get the low, mid and high band averages of indices in each minute. - for (int i = 0; i < width; i++) - { - // get the average of the three indices in the low bandwidth - var index = (indices1[0, i] + indices2[0, i] + indices3[0, i]) / 3; - Pen pen; - if (double.IsNaN(index)) - { - pen = new Pen(Color.Gray); - } - else - { - int red = (int)(255 * index); - if (red > 255) - { - red = 255; - } - - index = (indices1[1, i] + indices2[1, i] + indices3[1, i]) / 3; - int grn = (int)(255 * index); - if (grn > 255) - { - grn = 255; - } - - index = (indices1[2, i] + indices2[2, i] + indices3[2, i]) / 3; - int blu = (int)(255 * index); - if (blu > 255) - { - blu = 255; - } - - pen = new Pen(Color.FromArgb(red, grn, blu)); - } - - g.DrawLine(pen, i, 0, i, height); - } - - return image; - } - - /// - /// returns a LD spectrogram of same image length as the full-scale LDspectrogram but the frequency scale reduced to the passed vlaue of height. - /// This produces a LD spectrogram "ribbon" which can be used in circumstances where the full image is not appropriate. - /// Note that if the height passed is a power of 2, then the full frequency scale (also a power of 2 due to FFT) can be scaled down exactly. - /// A height of 32 is quite good - small but still discriminates frequency bands. - /// - public Image GetSpectrogramRibbon(string colorMap, int height) - { - string colourKeys = colorMap; - string[] keys = colourKeys.Split('-'); - - // get the matrices for each of the three indices. - double[,] indices1 = this.GetNormalisedSpectrogramMatrix(keys[0]); - double[,] indices2 = this.GetNormalisedSpectrogramMatrix(keys[1]); - double[,] indices3 = this.GetNormalisedSpectrogramMatrix(keys[2]); - - int width = indices1.GetLength(1); - var image = new Bitmap(width, height); - - // get the reduced spectra of indices in each minute. - // calculate the reduction factor i.e. freq bins per pixel row - int bandWidth = indices1.GetLength(0) / height; - - for (int i = 0; i < width; i++) - { - var spectrum1 = MatrixTools.GetColumn(indices1, i); - var spectrum2 = MatrixTools.GetColumn(indices2, i); - var spectrum3 = MatrixTools.GetColumn(indices3, i); - for (int h = 0; h < height; h++) - { - int start = h * bandWidth; - double[] subArray = DataTools.Subarray(spectrum1, start, bandWidth); - double index = subArray.Average(); - if (double.IsNaN(index)) - { - index = 0.5; - } - - int red = (int)(255 * index); - if (red > 255) - { - red = 255; - } - - subArray = DataTools.Subarray(spectrum2, start, bandWidth); - index = subArray.Average(); - if (double.IsNaN(index)) - { - index = 0.5; - } - - int grn = (int)(255 * index); - if (grn > 255) - { - grn = 255; - } - - subArray = DataTools.Subarray(spectrum3, start, bandWidth); - index = subArray.Average(); - if (double.IsNaN(index)) - { - index = 0.5; - } - - int blu = (int)(255 * index); - if (blu > 255) - { - blu = 255; - } - - image.SetPixel(i, h, Color.FromArgb(red, grn, blu)); - } - } - - return image; - } - - /// - /// This method not currently called but might be useful in future - /// - public double[] GetSummaryIndicesWeightedAtDistance(double[,] normalisedIndex1, double[,] normalisedIndex2, double[,] normalisedIndex3, int minuteInDay, int distanceInMeters) - { - double decayConstant = 20.0; - double[] indices1 = MatrixTools.GetColumn(normalisedIndex1, minuteInDay); - indices1 = DataTools.reverseArray(indices1); - indices1 = CalculateDecayedSpectralIndices(indices1, distanceInMeters, decayConstant); - - //double[] indices2 = MatrixTools.GetColumn(normalisedIndex2, minuteInDay); - //indices2 = DataTools.reverseArray(indices2); - //indices2 = CalculateDecayedSpectralIndices(indices2, distanceInMeters, decayConstant); - - //double[] indices3 = MatrixTools.GetColumn(normalisedIndex3, minuteInDay); - //indices3 = DataTools.reverseArray(indices3); - //indices3 = CalculateDecayedSpectralIndices(indices3, distanceInMeters, decayConstant); - - return indices1; // or indices2 or indices 3 - } - - public static double[] CalculateDecayedSpectralIndices(double[] spectralIndices, int distanceInMeters, double halfLife) - { - double log2 = Math.Log(2.0); - double differentialFrequencyDecay = 0.1; - - int length = spectralIndices.Length; - - double[] returned = new double[length]; - for (int i = 0; i < length; i++) - { - // half life decreases with increasing frequency. - // double frequencyDecay = differentialFrequencyDecay * i; - double tau = (halfLife - (differentialFrequencyDecay * i)) / log2; - - // check tau is not negative - if (tau < 0.0) - { - tau = 0.001; - } - - double exponent = distanceInMeters / tau; - returned[i] = spectralIndices[i] * Math.Pow(Math.E, -exponent); - } - - return returned; - } - //############################################################################################################################################################ //# STATIC METHODS ########################################################################################################################################### //############################################################################################################################################################ @@ -1214,7 +906,7 @@ public static Image DrawRgbColourMatrix(double[,] redM, double[,] grnM, double[, var bmp = new Bitmap(cols, rows, PixelFormat.Format24bppRgb); - const int maxRgbValue = 255; + //const int maxRgbValue = 255; for (int row = 0; row < rows; row++) { @@ -1224,7 +916,7 @@ public static Image DrawRgbColourMatrix(double[,] redM, double[,] grnM, double[, var g = grnM[row, column]; var b = bluM[row, column]; - // if any of the indices is blank/NaN then render as grey. + // if any of the indices is NaN then render as grey. if (double.IsNaN(r) || double.IsNaN(g) || double.IsNaN(b)) { r = 0.5; @@ -1576,18 +1268,31 @@ public static Tuple[] DrawSpectrogramsFromSpectralIndices( CreateTwoMapsImage(outputDirectory, fileStem, image1, imageX, image2); // AS OF DECEMBER 2018, no longer produce SUMMARY RIBBONS. Did not prove useful. - //ribbon = cs.GetSummaryIndexRibbon(colorMap1); - //var ribbon = cs1.GetSummaryIndexRibbonWeighted(colorMap1); + //double[,] m = cs1.GetNormalisedSpectrogramMatrix(key); + //var ribbon = LdSpectrogramRibbons.GetSummaryIndexRibbon(colorMap1); + //var ribbon = LdSpectrogramRibbons.GetSummaryIndexRibbonWeighted(colorMap1); //ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap1 + ".SummaryRibbon", "png")); - //ribbon = cs.GetSummaryIndexRibbon(colorMap2); - //ribbon = cs1.GetSummaryIndexRibbonWeighted(colorMap2); + //ribbon = LdSpectrogramRibbons.GetSummaryIndexRibbon(colorMap2); + //ribbon = LdSpectrogramRibbons.GetSummaryIndexRibbonWeighted(colorMap2); //ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap2 + ".SummaryRibbon", "png")); - // Spectrogram ribbons are very useful for viewing multiple days of recording. - var ribbon = cs1.GetSpectrogramRibbon(colorMap1, RibbonPlotHeight); - ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap1 + SpectralRibbonTag, "png")); - ribbon = cs1.GetSpectrogramRibbon(colorMap2, RibbonPlotHeight); - ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap2 + SpectralRibbonTag, "png")); + //NEXT produce SPECTROGRAM RIBBONS + // These are useful for viewing multiple consecutive days of recording. + // Get matrix for each of the three indices. + string[] keys1 = colorMap1.Split('-'); + double[,] indices1 = cs1.GetNormalisedSpectrogramMatrix(keys1[0]); + double[,] indices2 = cs1.GetNormalisedSpectrogramMatrix(keys1[1]); + double[,] indices3 = cs1.GetNormalisedSpectrogramMatrix(keys1[2]); + + var ribbon = LdSpectrogramRibbons.GetSpectrogramRibbon(indices1, indices2, indices3); + ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap1 + LdSpectrogramRibbons.SpectralRibbonTag, "png")); + + string[] keys2 = colorMap2.Split('-'); + indices1 = cs1.GetNormalisedSpectrogramMatrix(keys2[0]); + indices2 = cs1.GetNormalisedSpectrogramMatrix(keys2[1]); + indices3 = cs1.GetNormalisedSpectrogramMatrix(keys2[2]); + ribbon = LdSpectrogramRibbons.GetSpectrogramRibbon(indices1, indices2, indices3); + ribbon.Save(FilenameHelpers.AnalysisResultPath(outputDirectory, fileStem, colorMap2 + LdSpectrogramRibbons.SpectralRibbonTag, "png")); // only return images if chromeless return imageChrome == ImageChrome.Without diff --git a/src/AudioAnalysisTools/LongDurationSpectrograms/LdSpectrogramRibbons.cs b/src/AudioAnalysisTools/LongDurationSpectrograms/LdSpectrogramRibbons.cs new file mode 100644 index 000000000..71b8c5fbc --- /dev/null +++ b/src/AudioAnalysisTools/LongDurationSpectrograms/LdSpectrogramRibbons.cs @@ -0,0 +1,332 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). +// +// +// This class contains methods that work on spectral ribbons. + +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; + +namespace AudioAnalysisTools.LongDurationSpectrograms +{ + using System.Data; + using System.Drawing; + using System.IO; + using Acoustics.Shared.Csv; + using TowseyLibrary; + + public static class LdSpectrogramRibbons + { + public const string SpectralRibbonTag = ".SpectralRibbon"; + public const int RibbonPlotHeight = 32; + + /// + /// Reads. + /// + public static void ReadSpectralIndicesFromTwoFalseColourSpectrogramRibbons() + { + var path1 = new FileInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\Concat5\Testing\2015-11-14\Testing__ACI-ENT-EVN.SpectralRibbon.png"); + var path2 = new FileInfo(@"C:\Ecoacoustics\Output\Test\Test24HourRecording\Concat5\Testing\2015-11-14\Testing__BGN-PMN-SPT.SpectralRibbon.png"); + var outputPath = new FileInfo(@"C:\Ecoacoustics\Output\Test\TestReadingOfRibbonFiles\Test.csv"); + + // THis method assumes that the ribbon spectrograms are composed using the following five indices for RGB + //string[] colourKeys1 = { "ACI", "ENT", "EVN" }; + //string[] colourKeys2 = { "BGN", "PMN", "EVN" }; + + var image1 = Image.FromFile(path1.FullName); + var matrixList1 = ReadSpectralIndicesFromFalseColourSpectrogram((Bitmap)image1); + + // read second image file + var image2 = Image.FromFile(path2.FullName); + var matrixList2 = ReadSpectralIndicesFromFalseColourSpectrogram((Bitmap)image2); + + //set up the return Matrix containing 1440 rows and 5 x 32 indices + var rowCount = matrixList1[0].GetLength((0)); + var colCount = matrixList1[0].GetLength((1)); + var indexCount = colCount * 5; // 5 because will incorporate 5 indices + var matrix = new double[rowCount, indexCount]; + + // copy indices into return matrix + for (int r = 0; r < rowCount; r++) + { + // copy in ACI row + var row = MatrixTools.GetRow(matrixList1[0], r); + for (int c = 0; c < colCount; c++) + { + matrix[r, c] = row[c]; + } + + // copy in ENT row + row = MatrixTools.GetRow(matrixList1[1], r); + for (int c = 0; c < colCount; c++) + { + int startColumn = colCount; + matrix[r, startColumn + c] = row[c]; + } + + // copy in EVN row + row = MatrixTools.GetRow(matrixList1[2], r); + for (int c = 0; c < colCount; c++) + { + int startColumn = colCount * 2; + matrix[r, startColumn + c] = row[c]; + } + + // copy in BGN row + row = MatrixTools.GetRow(matrixList2[0], r); + for (int c = 0; c < colCount; c++) + { + int startColumn = colCount * 3; + matrix[r, startColumn + c] = row[c]; + } + + // copy in PMN row + row = MatrixTools.GetRow(matrixList2[1], r); + for (int c = 0; c < colCount; c++) + { + int startColumn = colCount * 4; + matrix[r, startColumn + c] = row[c]; + } + } + + //MatrixTools.WriteMatrix2File(matrix, outputPath.FullName); + Csv.WriteMatrixToCsv(outputPath, matrix); + } + + /// + /// Read in a false colour spectrogram ribbon and recover the normalised indices from the pixel values. + /// + /// a false colour spectrogram ribbon + /// an array of three index matrices, from red, green, blue components of each pixel. + public static List ReadSpectralIndicesFromFalseColourSpectrogram(Bitmap image) + { + var width = image.Width; + var height = image.Height; + var red = new double[width, height]; + var grn = new double[width, height]; + var blu = new double[width, height]; + + for (int w = 0; w < width; w++) + { + for (int h = 0; h < height; h++) + { + var pixel = image.GetPixel(w, height - h - 1); + red[w, h] = pixel.R / 255D; + grn[w, h] = pixel.G / 255D; + blu[w, h] = pixel.B / 255D; + } + } + + var list = new List + { + red, + grn, + blu, + }; + + return list; + } + + /// + /// returns a Long Duration spectrogram of same image length as the full-scale LDspectrogram but the frequency scale reduced to the passed vlaue of height. + /// This produces a LD spectrogram "ribbon" which can be used in circumstances where the full image is not appropriate. + /// Note that if the height passed is a power of 2, then the full frequency scale (also a power of 2 due to FFT) can be scaled down exactly. + /// A height of 32 is quite good - small but still discriminates frequency bands. + /// + public static Image GetSpectrogramRibbon(double[,] indices1, double[,] indices2, double[,] indices3) + { + int height = RibbonPlotHeight; + int width = indices1.GetLength(1); + var image = new Bitmap(width, height); + + // get the reduced spectra of indices in each minute. + // calculate the reduction factor i.e. freq bins per pixel row + int bandWidth = indices1.GetLength(0) / height; + + for (int i = 0; i < width; i++) + { + var spectrum1 = MatrixTools.GetColumn(indices1, i); + var spectrum2 = MatrixTools.GetColumn(indices2, i); + var spectrum3 = MatrixTools.GetColumn(indices3, i); + for (int h = 0; h < height; h++) + { + int start = h * bandWidth; + double[] subArray = DataTools.Subarray(spectrum1, start, bandWidth); + + // reduce full spectrum to ribbon by taking the AVERAGE of sub-bands. + // If the resulting value is NaN, then set the colour to grey by setting index to 0.5. + double index = subArray.Average(); + if (double.IsNaN(index)) + { + index = 0.5; + } + + int red = (int)(255 * index); + if (red > 255) + { + red = 255; + } + + subArray = DataTools.Subarray(spectrum2, start, bandWidth); + index = subArray.Average(); + if (double.IsNaN(index)) + { + index = 0.5; + } + + int grn = (int)(255 * index); + if (grn > 255) + { + grn = 255; + } + + subArray = DataTools.Subarray(spectrum3, start, bandWidth); + index = subArray.Average(); + if (double.IsNaN(index)) + { + index = 0.5; + } + + int blu = (int)(255 * index); + if (blu > 255) + { + blu = 255; + } + + image.SetPixel(i, h, Color.FromArgb(red, grn, blu)); + } + } + + return image; + } + + //############################################################################################################################################################ + //# BELOW METHODS CALCULATE SUMMARY INDEX RIBBONS ############################################################################################################ + //# NOTE As of 2018, summary index ribbons are no longer produced. An idea that did not work! + //# WARNING: THE BELOW METHODS WILL PROBABLY NOT WORK DUE TO SUBSEQUENT REFACTORING OF LDSpectrogramRGB class. + + /// + /// Returns an array of summary indices, where each element of the array (one element per minute) is a single summary index + /// derived by averaging the spectral indices for that minute. + /// The returned matrices have spectrogram orientation. + /// + public static double[] GetSummaryIndexArray(double[,] m) + { + int colcount = m.GetLength(1); + double[] indices = new double[colcount]; + + for (int r = 0; r < colcount; r++) + { + indices[r] = MatrixTools.GetColumn(m, r).Average(); + } + + return indices; + } + + public static Image GetSummaryIndexRibbon(double[] indices1, double[] indices2, double[] indices3) + { + int width = indices1.Length; + int height = SpectrogramConstants.HEIGHT_OF_TITLE_BAR; + var image = new Bitmap(width, height); + var g = Graphics.FromImage(image); + for (int i = 0; i < width; i++) + { + Pen pen; + if (double.IsNaN(indices1[i]) || double.IsNaN(indices2[i]) || double.IsNaN(indices3[i])) + { + pen = new Pen(Color.Gray); + } + else + { + int red = (int)(255 * indices1[i]); + int grn = (int)(255 * indices2[i]); + int blu = (int)(255 * indices3[i]); + pen = new Pen(Color.FromArgb(red, grn, blu)); + } + + g.DrawLine(pen, i, 0, i, height); + } + + return image; + } + + public static Image GetSummaryIndexRibbonWeighted(double[,] indices1, double[,] indices2, double[,] indices3) + { + int width = indices1.GetLength(1); + int height = SpectrogramConstants.HEIGHT_OF_TITLE_BAR; + var image = new Bitmap(width, height); + var g = Graphics.FromImage(image); + + // get the low, mid and high band averages of indices in each minute. + for (int i = 0; i < width; i++) + { + // get the average of the three indices in the low bandwidth + var index = (indices1[0, i] + indices2[0, i] + indices3[0, i]) / 3; + Pen pen; + if (double.IsNaN(index)) + { + pen = new Pen(Color.Gray); + } + else + { + int red = (int)(255 * index); + if (red > 255) + { + red = 255; + } + + index = (indices1[1, i] + indices2[1, i] + indices3[1, i]) / 3; + int grn = (int)(255 * index); + if (grn > 255) + { + grn = 255; + } + + index = (indices1[2, i] + indices2[2, i] + indices3[2, i]) / 3; + int blu = (int)(255 * index); + if (blu > 255) + { + blu = 255; + } + + pen = new Pen(Color.FromArgb(red, grn, blu)); + } + + g.DrawLine(pen, i, 0, i, height); + } + + return image; + } + + public static double[] CalculateDecayedSpectralIndices(double[] spectralIndices, int distanceInMeters, double halfLife) + { + double log2 = Math.Log(2.0); + double differentialFrequencyDecay = 0.1; + + int length = spectralIndices.Length; + + double[] returned = new double[length]; + for (int i = 0; i < length; i++) + { + // half life decreases with increasing frequency. + // double frequencyDecay = differentialFrequencyDecay * i; + double tau = (halfLife - (differentialFrequencyDecay * i)) / log2; + + // check tau is not negative + if (tau < 0.0) + { + tau = 0.001; + } + + double exponent = distanceInMeters / tau; + returned[i] = spectralIndices[i] * Math.Pow(Math.E, -exponent); + } + + return returned; + } + } +}