/// <summary> /// Converts summary indices to a tracks image, one track for each index. /// </summary> public static Image <Rgb24> DrawImageOfSummaryIndices( Dictionary <string, IndexProperties> listOfIndexProperties, Dictionary <string, double[]> dictionaryOfSummaryIndices, string titleText, TimeSpan indexCalculationDuration, DateTimeOffset?recordingStartDate, List <GapsAndJoins> errors = null, bool verbose = true) { const int trackHeight = DefaultTrackHeight; int scaleLength = 0; var backgroundColour = Color.White; // init list of bitmap images to store image tracks var bitmapList = new List <Tuple <IndexProperties, Image <Rgb24> > >(dictionaryOfSummaryIndices.Keys.Count); // set up strings to store info about which indices are used var s1 = new StringBuilder("Indices not found:"); var s2 = new StringBuilder("Indices not plotted:"); // accumulate the individual tracks in a List foreach (string key in dictionaryOfSummaryIndices.Keys) { if (!listOfIndexProperties.ContainsKey(key)) { s1.Append(" {0},".Format2(key)); continue; } IndexProperties ip = listOfIndexProperties[key]; if (!ip.DoDisplay) { s2.Append(" {0},".Format2(key)); continue; } //string name = ip.Name; double[] array = dictionaryOfSummaryIndices[key]; scaleLength = array.Length; // alternate rows have different colour to make tracks easier to read backgroundColour = backgroundColour == Color.LightGray ? Color.White : Color.LightGray; var bitmap = ip.GetPlotImage(array, backgroundColour, errors); bitmapList.Add(Tuple.Create(ip, bitmap)); } if (verbose) { Logger.Warn(s1.ToString()); Logger.Warn(s2.ToString()); } var listOfBitmaps = bitmapList // .OrderBy(tuple => tuple.Item1.Order) // don't order because want to preserve alternating gray/white rows. .Select(tuple => tuple.Item2) .Where(b => b != null).ToList(); //set up the composite image parameters int x_offset = 2; int graphWidth = x_offset + scaleLength; int imageWidth = x_offset + scaleLength + TrackEndPanelWidth; Image <Rgb24> titleBmp = ImageTrack.DrawTitleTrack(imageWidth, trackHeight, titleText); TimeSpan xAxisPixelDuration = indexCalculationDuration; TimeSpan fullDuration = TimeSpan.FromTicks(xAxisPixelDuration.Ticks * graphWidth); Image <Rgb24> timeBmp1 = ImageTrack.DrawTimeRelativeTrack(fullDuration, graphWidth, trackHeight); Image <Rgb24> timeBmp2 = timeBmp1; DateTimeOffset?dateTimeOffset = recordingStartDate; if (dateTimeOffset.HasValue) { // draw extra time scale with absolute start time. AND THEN Do SOMETHING WITH IT. timeBmp2 = ImageTrack.DrawTimeTrack(fullDuration, dateTimeOffset, graphWidth, trackHeight); } //draw the composite bitmap var imageList = new List <Image <Rgb24> > { titleBmp, timeBmp1, }; foreach (var image in listOfBitmaps) { imageList.Add(image); } imageList.Add(timeBmp2); var compositeBmp = (Image <Rgb24>)ImageTools.CombineImagesVertically(imageList); return(compositeBmp); }
// ############################################################################################################## // ######################### 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")); }
/// <summary> /// Converts summary indices to a tracks image /// </summary> /// <param name="listOfIndexProperties"></param> /// <param name="dictionaryOfSummaryIndices"></param> /// <param name="titleText"></param> /// <param name="indexCalculationDuration"></param> /// <param name="recordingStartDate"></param> /// <param name="sunriseDataFile"></param> /// <param name="errors"></param> public static Bitmap DrawImageOfSummaryIndices( Dictionary <string, IndexProperties> listOfIndexProperties, Dictionary <string, double[]> dictionaryOfSummaryIndices, string titleText, TimeSpan indexCalculationDuration, DateTimeOffset?recordingStartDate, FileInfo sunriseDataFile = null, List <GapsAndJoins> errors = null, bool verbose = false) { // to translate past keys into current keys Dictionary <string, string> translationDictionary = InitialiseIndexProperties.GetKeyTranslationDictionary(); const int trackHeight = DefaultTrackHeight; int scaleLength = 0; var bitmapList = new List <Tuple <IndexProperties, Image> >(dictionaryOfSummaryIndices.Keys.Count); // accumulate the individual tracks in a List foreach (string key in dictionaryOfSummaryIndices.Keys) { string correctKey = key; if (!listOfIndexProperties.ContainsKey(key)) { if (translationDictionary.ContainsKey(key)) { correctKey = translationDictionary[key]; LoggedConsole.WriteWarnLine( "The csv header is an unknown index <{0}>. Translated to <{1}>", key, correctKey); } else { if (verbose) { Logger.Warn( "A index properties configuration could not be found for {0} (not even in the translation directory). Property is ignored and not rendered" .Format2(key)); } continue; } } IndexProperties ip = listOfIndexProperties[correctKey]; if (!ip.DoDisplay) { continue; } string name = ip.Name; double[] array = dictionaryOfSummaryIndices[key]; scaleLength = array.Length; Image bitmap = ip.GetPlotImage(array, errors); bitmapList.Add(Tuple.Create(ip, bitmap)); } var listOfBitmaps = bitmapList .OrderBy(tuple => tuple.Item1.Order) .Select(tuple => tuple.Item2) .Where(b => b != null).ToList(); //set up the composite image parameters int X_offset = 2; int graphWidth = X_offset + scaleLength; int imageWidth = X_offset + scaleLength + TrackEndPanelWidth; TimeSpan scaleDuration = TimeSpan.FromMinutes(scaleLength); int imageHt = trackHeight * (listOfBitmaps.Count + 4); //+3 for title and top and bottom time tracks Bitmap titleBmp = ImageTrack.DrawTitleTrack(imageWidth, trackHeight, titleText); //Bitmap time1Bmp = ImageTrack.DrawTimeTrack(scaleDuration, TimeSpan.Zero, DrawSummaryIndices.TimeScale, graphWidth, TrackHeight, "Time (hours)"); TimeSpan xAxisPixelDuration = indexCalculationDuration; TimeSpan fullDuration = TimeSpan.FromTicks(xAxisPixelDuration.Ticks * graphWidth); Bitmap timeBmp1 = ImageTrack.DrawTimeRelativeTrack(fullDuration, graphWidth, trackHeight); Bitmap timeBmp2 = timeBmp1; Bitmap suntrack = null; DateTimeOffset?dateTimeOffset = recordingStartDate; if (dateTimeOffset.HasValue) { // draw extra time scale with absolute start time. AND THEN Do SOMETHING WITH IT. timeBmp2 = ImageTrack.DrawTimeTrack(fullDuration, dateTimeOffset, graphWidth, trackHeight); suntrack = SunAndMoon.AddSunTrackToImage(scaleLength, dateTimeOffset, sunriseDataFile); } //draw the composite bitmap var imageList = new List <Image>(); imageList.Add(titleBmp); imageList.Add(timeBmp1); for (int i = 0; i < listOfBitmaps.Count; i++) { imageList.Add(listOfBitmaps[i]); } imageList.Add(timeBmp2); imageList.Add(suntrack); Bitmap compositeBmp = (Bitmap)ImageTools.CombineImagesVertically(imageList); return(compositeBmp); }
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()