# ConfigurationTool.cs ``` cs /// Author : Maxime Rohmer /// Date : 30/05/2023 /// File : ConfigurationTool.cs /// Brief : Class that contains all the methods used to create config files for the main programm /// Version : Alpha 1.0 using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using Tesseract; using System.IO; using System.Text.Json; using System.Text.Json.Nodes; namespace TrackTrends { public class ConfigurationTool { public Zone MainZone; public const int NUMBER_OF_DRIVERS = 20; public const int NUMBER_OF_ZONES = 9; public const string CONFIGS_FOLDER_NAME = "./Presets/"; /// /// Creates the configuration tool. It can only be created if you already have the dimensions of the main zone /// /// The full image coming from the F1TV Data Channel /// The dimensions of the zone where all the drivers data are situated public ConfigurationTool(Bitmap fullImage, Rectangle mainZoneDimensions) { MainZone = new Zone(fullImage, mainZoneDimensions,"Main"); AutoCalibrate(); } /// /// Resets the main zone /// public void ResetMainZone() { MainZone.ResetZones(); } /// /// Reset the windows /// public void ResetWindows() { MainZone.ResetWindows(); } /// /// Save the current config in a JSON file stored in /Presets/ /// /// A list of all the drivers in the GP. IMPORTANT, they need to ALL be mentionned or the program wont be able to detect the missing ones and will F up everything /// The name the config should have public void SaveToJson(List drivers, string configName) { string JSON = ""; JsonObject jsonFileObject = new JsonObject(); //Creates the mainZone object JsonObject mainZoneObject = new JsonObject(); mainZoneObject.Add("x",MainZone.Bounds.X); mainZoneObject.Add("y",MainZone.Bounds.Y); mainZoneObject.Add("width",MainZone.Bounds.Width); mainZoneObject.Add("height",MainZone.Bounds.Height); JsonArray driverZonesArray = new JsonArray(); //Creates all the subzones that contain driver infos int DriverID = 0; foreach (Zone driverZone in MainZone.Zones) { DriverID++; JsonObject driverZoneObject = new JsonObject(); driverZoneObject.Add("name","Driver"+DriverID); driverZoneObject.Add("x", driverZone.Bounds.X); driverZoneObject.Add("y", driverZone.Bounds.Y); driverZoneObject.Add("width", driverZone.Bounds.Width); driverZoneObject.Add("height", driverZone.Bounds.Height); JsonArray windowsArray = new JsonArray(); JsonObject windowObject = new JsonObject(); //Creates all the windows of the current driver zone //Note : We store ALL the windows and zones in the JSON because they are not spaced exactly the same on the main zone foreach (Window window in driverZone.Windows) { windowObject.Add(window.Name, new JsonObject { { "x", window.Bounds.X }, { "y", window.Bounds.Y }, { "width", window.Bounds.Width }, { "height", window.Bounds.Height } }); } windowsArray.Add(windowObject); driverZoneObject.Add("Windows",windowsArray); driverZonesArray.Add(driverZoneObject); } mainZoneObject.Add("DriverZones",driverZonesArray); JsonArray driversArray = new JsonArray(); foreach (string driver in drivers) { driversArray.Add(driver); } mainZoneObject.Add("Drivers",driversArray); jsonFileObject.Add("Main",mainZoneObject); JSON = jsonFileObject.ToString(); //Saving the file string path = CONFIGS_FOLDER_NAME + configName; if (File.Exists(path + ".json")) { //We need to create a new name int count = 2; while (File.Exists(path + "_" + count + ".json")) { count++; } path += "_" + count + ".json"; } else { path += ".json"; } File.WriteAllText(path, JSON); } /// /// Adds a window in the windows list /// Be carefull of the order. It cant be random or it will crash. The programm expect the first to be position, second Gap to leader etc... /// /// The bounds of the window public void AddWindows(List rectangles) { foreach (Zone driverZone in MainZone.Zones) { Bitmap zoneImage = driverZone.ZoneImage; for (int i = 1; i <= rectangles.Count; i++) { switch (i) { case 1: //First zone should be the driver's Position driverZone.AddWindow(new DriverPositionWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 2: //Second zone should be the Gap to leader driverZone.AddWindow(new DriverGapToLeaderWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 3: //Third zone should be the driver's Lap Time driverZone.AddWindow(new DriverLapTimeWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 4: //Fourth zone should be the driver's DRS status driverZone.AddWindow(new DriverDrsWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 5: //Fifth zone should be the driver's Tyre's informations driverZone.AddWindow(new DriverTyresWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 6: //Sixth zone should be the driver's Name driverZone.AddWindow(new DriverNameWindow(driverZone.ZoneImage, rectangles[i - 1], false)); break; case 7: //Seventh zone should be the driver's First Sector driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 1, false)); break; case 8: //Zone number eight should be the driver's Second Sector driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 2, false)); break; case 9: //Zone number nine should be the driver's Position Sector driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 3, false)); break; } } } } /// /// This will automatically create all the driver zones at the correct places if the main zone has been weel positionned /// You cant just divide the image by the number of pilots or it will be messy and inconsistent at the end (Garbage in Garbage Out) /// public void AutoCalibrate() { List detectedText = new List(); List zones = new List(); TesseractEngine engine = new TesseractEngine(Window.TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default); Image image = MainZone.ZoneImage; var tessImage = Pix.LoadFromMemory(Window.ImageToByte(image)); Page page = engine.Process(tessImage); //Runs a quick OCR detection. Not to detect any content but just to detect where is all the text positionned. //For each row it decides the best Zone location and adds it to the Driver zone list using (var iter = page.GetIterator()) { iter.Begin(); do { Rect boundingBox; if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox)) { //We remove all the rectangles that are definitely too big if (boundingBox.Height < image.Height / NUMBER_OF_DRIVERS) { //Now we add a filter to only get the boxes in the right because they are much more reliable in size if (boundingBox.X1 > image.Width / 2) { //Now we check if an other square box has been found roughly in the same y axis bool match = false; //The tolerance is roughly half the size that a window will be int tolerance = (image.Height / NUMBER_OF_DRIVERS) / 2; foreach (Rectangle rect in detectedText) { if (rect.Y > boundingBox.Y1 - tolerance && rect.Y < boundingBox.Y1 + tolerance) { //There already is a rectangle in this line match = true; } } //if nothing matched we can add it if (!match) detectedText.Add(new Rectangle(boundingBox.X1, boundingBox.Y1, boundingBox.Width, boundingBox.Height)); } } } } while (iter.Next(PageIteratorLevel.Word)); } //DEBUG int i = 1; foreach (Rectangle Rectangle in detectedText) { Rectangle windowRectangle; Size windowSize = new Size(image.Width, image.Height / NUMBER_OF_DRIVERS); Point windowLocation = new Point(0, (Rectangle.Y + Rectangle.Height / 2) - windowSize.Height / 2); windowRectangle = new Rectangle(windowLocation, windowSize); //We add the driver zones Zone driverZone = new Zone(MainZone.ZoneImage, windowRectangle, "DriverZone"); MainZone.AddZone(driverZone); //driverZone.ZoneImage.Save("Driver" + i+".png"); i++; } } } } ```