# Reader.cs ``` cs /// Author : Maxime Rohmer /// Date : 30/05/2023 /// File : Reader.cs /// Brief : Class used to Read the config file for the OCR /// Version : Alpha 1.0 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; using System.Windows.Forms; using System.IO; using System.Text.Json; namespace TrackTrends { public class Reader { const int NUMBER_OF_DRIVERS = 20; public List Drivers; public List MainZones; private SqliteStorage _storage; private List[] DriverDataLogs = new List[NUMBER_OF_DRIVERS]; private int[] DriverLaps = new int[NUMBER_OF_DRIVERS]; public SqliteStorage Storage { get => _storage; private set => _storage = value; } public Reader(string configFile, Bitmap image, bool loadOCR = true) { Storage = new SqliteStorage(); MainZones = Load(image, configFile, ref Drivers, loadOCR); } /// /// Method that reads the JSON config file and create all the Zones and Windows /// /// The image #id on wich you want to create the zones on public List Load(Bitmap image, string configFilePath, ref List driverListToFill, bool LoadOCR) { // Note : You may wonder why in the H... I have all the zones and windows stored in a JSON file and not just for example the first and the last // Its because they are not perfectly aligned to each others and every zone has his own alignement to the main image List mainZones = new List(); Bitmap fullImage = image; Zone mainZone; for (int i = 0; i < NUMBER_OF_DRIVERS; i++) { DriverDataLogs[i] = new List(); DriverLaps[i] = 0; } try { string jsonString = File.ReadAllText(configFilePath); JsonDocument document = JsonDocument.Parse(jsonString); JsonElement root = document.RootElement; mainZones = new List(); driverListToFill = new List(); JsonElement main = root.GetProperty("Main"); int x = main.GetProperty("x").GetInt32(); int y = main.GetProperty("y").GetInt32(); int width = main.GetProperty("width").GetInt32(); int height = main.GetProperty("height").GetInt32(); mainZone = new Zone(fullImage, new Rectangle(x, y, width, height), "Main"); mainZone.ResetWindows(); mainZone.ResetZones(); JsonElement driverZones = main.GetProperty("DriverZones"); foreach (JsonElement driverZoneElement in driverZones.EnumerateArray()) { string name = driverZoneElement.GetProperty("name").GetString(); int driverX = driverZoneElement.GetProperty("x").GetInt32() + mainZone.Bounds.X; int driverY = driverZoneElement.GetProperty("y").GetInt32() + mainZone.Bounds.Y; int driverWidth = driverZoneElement.GetProperty("width").GetInt32(); int driverHeight = driverZoneElement.GetProperty("height").GetInt32(); Zone driverZone = new Zone(fullImage, new Rectangle(driverX, driverY, driverWidth, driverHeight), "Driver"); JsonElement windowsElement = driverZoneElement.GetProperty("Windows"); //string[] windowNames = new string[] { "Position","GapToLeader","LapTime","DRS","Tyres","Name","Sector1","Sector2","Sector3" }; foreach (JsonElement windowElement in windowsElement.EnumerateArray()) { //Position JsonElement posEl = windowElement.GetProperty("Position"); DriverPositionWindow positionWindow = new DriverPositionWindow(driverZone.ZoneImage, new Rectangle( posEl.GetProperty("x").GetInt32(), posEl.GetProperty("y").GetInt32(), posEl.GetProperty("width").GetInt32(), posEl.GetProperty("height").GetInt32()), LoadOCR); //GapToLeader JsonElement gapEl = windowElement.GetProperty("GapToLeader"); DriverGapToLeaderWindow gapWindow = new DriverGapToLeaderWindow(driverZone.ZoneImage, new Rectangle( gapEl.GetProperty("x").GetInt32(), gapEl.GetProperty("y").GetInt32(), gapEl.GetProperty("width").GetInt32(), gapEl.GetProperty("height").GetInt32()), LoadOCR); //LapTime JsonElement lapEl = windowElement.GetProperty("LapTime"); DriverLapTimeWindow lapWindow = new DriverLapTimeWindow(driverZone.ZoneImage, new Rectangle( lapEl.GetProperty("x").GetInt32(), lapEl.GetProperty("y").GetInt32(), lapEl.GetProperty("width").GetInt32(), lapEl.GetProperty("height").GetInt32()), LoadOCR); //DRS JsonElement drsEl = windowElement.GetProperty("DRS"); DriverDrsWindow drsWindow = new DriverDrsWindow(driverZone.ZoneImage, new Rectangle( drsEl.GetProperty("x").GetInt32(), drsEl.GetProperty("y").GetInt32(), drsEl.GetProperty("width").GetInt32(), drsEl.GetProperty("height").GetInt32()), LoadOCR); //Tyre JsonElement tyresEl = windowElement.GetProperty("Tyres"); DriverTyresWindow tyreWindow = new DriverTyresWindow(driverZone.ZoneImage, new Rectangle( tyresEl.GetProperty("x").GetInt32(), tyresEl.GetProperty("y").GetInt32(), tyresEl.GetProperty("width").GetInt32(), tyresEl.GetProperty("height").GetInt32()), LoadOCR); //Name JsonElement nameEl = windowElement.GetProperty("Name"); DriverNameWindow nameWindow = new DriverNameWindow(driverZone.ZoneImage, new Rectangle( nameEl.GetProperty("x").GetInt32(), nameEl.GetProperty("y").GetInt32(), nameEl.GetProperty("width").GetInt32(), nameEl.GetProperty("height").GetInt32()), LoadOCR); //Sector1 JsonElement sec1El = windowElement.GetProperty("Sector1"); DriverSectorWindow sec1Window = new DriverSectorWindow(driverZone.ZoneImage, new Rectangle( sec1El.GetProperty("x").GetInt32(), sec1El.GetProperty("y").GetInt32(), sec1El.GetProperty("width").GetInt32(), sec1El.GetProperty("height").GetInt32()), 1, LoadOCR); //Sector2 JsonElement sec2El = windowElement.GetProperty("Sector2"); DriverSectorWindow sec2Window = new DriverSectorWindow(driverZone.ZoneImage, new Rectangle( sec2El.GetProperty("x").GetInt32(), sec2El.GetProperty("y").GetInt32(), sec2El.GetProperty("width").GetInt32(), sec2El.GetProperty("height").GetInt32()), 2, LoadOCR); //Sector3 JsonElement sec3El = windowElement.GetProperty("Sector3"); DriverSectorWindow sec3Window = new DriverSectorWindow(driverZone.ZoneImage, new Rectangle( sec3El.GetProperty("x").GetInt32(), sec3El.GetProperty("y").GetInt32(), sec3El.GetProperty("width").GetInt32(), sec3El.GetProperty("height").GetInt32()), 3, LoadOCR); driverZone.AddWindow(positionWindow); driverZone.AddWindow(gapWindow); driverZone.AddWindow(lapWindow); driverZone.AddWindow(drsWindow); driverZone.AddWindow(tyreWindow); driverZone.AddWindow(nameWindow); driverZone.AddWindow(sec1Window); driverZone.AddWindow(sec2Window); driverZone.AddWindow(sec3Window); } mainZone.AddZone(driverZone); } JsonElement driversElement = main.GetProperty("Drivers"); foreach (JsonElement driverElement in driversElement.EnumerateArray()) { string driverName = driverElement.GetString(); driverListToFill.Add(driverName); Storage.AddDriver(driverName); } mainZones.Add(mainZone); } catch (IOException ex) { MessageBox.Show("Error reading JSON file: " + ex.Message); } catch (JsonException ex) { MessageBox.Show("Invalid JSON format: " + ex.Message); } int driverID = 0; foreach (Zone z in mainZones[0].Zones) { driverID++; z.ZoneImage.Save("LoadedDriver" + driverID + ".png"); } return mainZones; } /// /// Method that calls all the zones and windows to get the content they can find on the image to display them /// /// The id of the image we are working with /// a string representation of all the returns public List Decode(List mainZones, List drivers) { List mainResults = new List(); //Decode for (int mainZoneId = 0; mainZoneId < mainZones.Count; mainZoneId++) { switch (mainZoneId) { case 0: //object lockObject = new object(); //Main Zone Parallel.For(0, mainZones[mainZoneId].Zones.Count, async i => //for (int i = 0; i < mainZones[mainZoneId].Zones.Count; i++) { DriverData data = mainZones[mainZoneId].Zones[i].Decode(new List(drivers)); mainResults.Add(data); DriverDataLogs[i].Add(data); if (data.Position != -1 && DriverDataLogs[i].Count > 1) { //Tries to fix the tyres if (data.CurrentTyre.NumberOfLaps > DriverDataLogs[i][DriverDataLogs[i].Count - 2].CurrentTyre.NumberOfLaps + 3) data.CurrentTyre.NumberOfLaps = DriverDataLogs[i][DriverDataLogs[i].Count - 2].CurrentTyre.NumberOfLaps + 1; //Checking if its a new lap //If the third sector is filled but it was'nt the last time, then it means that a new Lap has been started //Lap detection can be f***ed if the OCR takes so much time that an entire sector can be raced without us knowing. if ( DriverDataLogs[i][DriverDataLogs[i].Count - 1].Sector3 != 0 && DriverDataLogs[i][DriverDataLogs[i].Count - 2].Sector3 == 0 && DriverDataLogs[i][DriverDataLogs[i].Count - 2].Position != -1 && DriverDataLogs[i][DriverDataLogs[i].Count - 1].Position != -1) { DriverData stats = new DriverData(); stats = DriverDataLogs[i][DriverDataLogs[i].Count - 1]; DriverLaps[i]++; Storage.AddDriverStat(stats, DriverLaps[i]); } //Checking if its a pitstop //Forget this the best way to know if a tyre has been changed is if the number of laps is zero if (data.CurrentTyre.Coumpound != Tyre.Type.Undefined && data.CurrentTyre.NumberOfLaps == 0 && DriverDataLogs[i][DriverDataLogs[i].Count - 2].CurrentTyre.NumberOfLaps != 0) { Storage.AddPitstop(data.Name, DriverLaps[i] - 1, data.CurrentTyre.Coumpound.ToString()); //Driver laps -1 because it would take AT LEAST one lap for this program to detect a pitstop } } DriverDataLogs[i].Add(data); }); break; //Next there could be a Title Zone and TrackInfoZone } } //mainResults = mainResults.OrderBy(driver => driver.Position >= 0).ThenBy(driver => driver.Position).ToList(); mainResults = mainResults.OrderBy(driver => driver.Position).ToList(); return mainResults; } /// /// Changes the image in all of the zones wich then will do the same for theyre own subzones and windows /// /// The new Image from the F1TV data channel public void ChangeImage(Bitmap Image) { foreach (Zone z in MainZones) { z.Image = Image; } } /// /// Method that can be used to convert an amount of miliseconds into a more readable human form /// /// The given amount of miliseconds ton convert /// A human readable string that represents the ms public static string ConvertMsToTime(int amountOfMs) { //Convert.ToInt32 would round upand I dont want that int minuts = (int)((float)amountOfMs / (1000f * 60f)); int seconds = (int)((amountOfMs - (minuts * 60f * 1000f)) / 1000); int ms = amountOfMs - ((minuts * 60 * 1000) + (seconds * 1000)); return minuts + ":" + seconds.ToString("00") + ":" + ms.ToString("000"); } /// /// Old method that can draw on an image where the windows and zones are created. mostly used for debugging /// /// the #id of the image we are working with /// the drawed bitmap public Bitmap Draw(Bitmap image, List mainZones) { Graphics g = Graphics.FromImage(image); foreach (Zone z in mainZones) { int count = 0; foreach (Zone zz in z.Zones) { g.DrawRectangle(Pens.Red, z.Bounds); foreach (Window w in zz.Windows) { g.DrawRectangle(Pens.Blue, new Rectangle(z.Bounds.X + zz.Bounds.X, z.Bounds.Y + zz.Bounds.Y, zz.Bounds.Width, zz.Bounds.Height)); } count++; } } return image; } } } ```