Beispiel #1
0
        public static bool IsFileValidInternal(string Filename)
        {
            using var Trace = new Trace();  //This c# 8.0 using feature will auto dispose when the function is done.

            bool Ret = false;

            try
            {
                if (File.Exists(Filename))
                {
                    FileInfo fi = new FileInfo(Filename);
                    if (fi.Length > 800)
                    {
                        //try to prevent multiple threads from erroring out writing the json file...
                        Global.WaitFileAccessResult result = Global.WaitForFileAccess(Filename, FileAccess.Read, FileShare.ReadWrite, 5000);
                        if (result.Success)
                        {
                            //check its contents, 0 bytes indicate corruption
                            string contents = File.ReadAllText(Filename);
                            if (!contents.Contains("\0"))
                            {
                                if (contents.TrimStart().StartsWith("{") && contents.TrimEnd().EndsWith("}"))
                                {
                                    Ret = true;
                                }
                                else
                                {
                                    Log($"Error: Settings file does not look like JSON (size={fi.Length} bytes): {Filename}");
                                }
                            }
                            else
                            {
                                Log("Error: Settings file contains null bytes, corrupt: (size={fi.Length} bytes)" + Filename);
                            }
                        }
                        else
                        {
                            Log($"Error: Could not gain access to file for {result.TimeMS}ms - {Filename}");
                        }
                    }
                    else
                    {
                        Log($"Error: Settings file is too small at {fi.Length} bytes: {Filename}");
                    }
                }

                else
                {
                    Log("Settings file does not exist yet: " + Filename);
                }
            }
            catch (Exception ex)
            {
                Log($"Error: While validating settings file '{Filename}' got error '{ex.Message}'.");
            }
            return(Ret);
        }
        private List <ClsLogItm> LoadLogFile(string Filename, bool Import, bool LimitEntries)
        {
            List <ClsLogItm> ret = new List <ClsLogItm>();

            //this.LastLoadMessages.Clear();

            if (Import)
            {
                this.Enabled = false;  //disable while we import
            }
            string ExtractZipPath = "";
            string file           = Path.GetFileName(Filename);

            Stopwatch sw = Stopwatch.StartNew();

            if (File.Exists(Filename))
            {
                try
                {
                    Global.UpdateProgressBar($"Loading {Path.GetFileName(Filename)}...", 1, 1, 1);

                    Global.WaitFileAccessResult result = Global.WaitForFileAccess(Filename, FileAccess.Read, FileShare.Read, 30000, 20);
                    if (result.Success)
                    {
                        //if its a zip file, extract that puppy...
                        string NewFilename = "";

                        if (Filename.EndsWith("zip", StringComparison.OrdinalIgnoreCase))
                        {
                            ExtractZipPath = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "_" + file);
                            if (!Directory.Exists(ExtractZipPath))
                            {
                                Directory.CreateDirectory(ExtractZipPath);
                            }

                            //just extract the first file in the archive
                            using (ZipArchive archive = ZipFile.OpenRead(Filename))
                            {
                                foreach (ZipArchiveEntry entry in archive.Entries)
                                {
                                    string destinationPath = Path.GetFullPath(Path.Combine(ExtractZipPath, entry.FullName));
                                    entry.ExtractToFile(destinationPath, true);
                                    NewFilename = destinationPath;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            NewFilename = Filename;
                        }

                        lock (this._LockObj)
                        {
                            file = Path.GetFileName(NewFilename);
                            int  Invalid = 0;
                            bool OldFile = false;
                            using (StreamReader sr = new StreamReader(NewFilename))
                            {
                                int cnt = 0;
                                while (!sr.EndOfStream)
                                {
                                    cnt++;
                                    if (cnt > 1)
                                    {
                                        string line = sr.ReadLine();

                                        if (!OldFile && line.TrimStart().StartsWith("["))  //old log format, ignore
                                        {
                                            OldFile = true;
                                            this._LastIDX.WriteFullFence(0);
                                            break;
                                        }

                                        if (!Import)
                                        {
                                            //just spit out a list of log lines
                                            ClsLogItm CLI = new ClsLogItm(line);
                                            if (CLI.Level != LogLevel.Off)  //off indicates invalid - for example the old log format
                                            {
                                                CLI.FromFile = true;
                                                CLI.Filename = file;
                                                ret.Add(CLI);
                                            }
                                            else
                                            {
                                                Invalid++;
                                                if (Invalid > 50)
                                                {
                                                    this.Log($"Error: Too many invalid lines ({Invalid}) stopping load.");
                                                    ret.Clear();
                                                    break;
                                                }
                                                else
                                                {
                                                    this.Log($"Debug: {Invalid} line(s) in log file '{line}'");
                                                }
                                            }
                                        }
                                        else
                                        {
                                            //load into current log manager
                                            if (this._Store)
                                            {
                                                ClsLogItm CLI = new ClsLogItm(line);
                                                if (CLI.Level != LogLevel.Off)  //off indicates invalid - for example the old log format
                                                {
                                                    this.LastLogItm = CLI;
                                                    if (this.LastLogItm.Level >= this.MinLevel)
                                                    {
                                                        CLI.FromFile = true;
                                                        CLI.Filename = file;
                                                        this.Values.Add(this.LastLogItm);
                                                        this.RecentlyAdded.Enqueue(this.LastLogItm);
                                                        //keep the log list size down
                                                        if (LimitEntries && this.Values.Count > this.MaxGUILogItems)
                                                        {
                                                            this.RecentlyDeleted.Enqueue(this.Values[0]);
                                                            this.Values.RemoveAt(0);
                                                        }
                                                    }
                                                }
                                                else
                                                {
                                                    Invalid++;
                                                    if (Invalid > 50)
                                                    {
                                                        this.Log($"Error: Too many invalid lines ({Invalid}) stopping load.");
                                                        ret.Clear();
                                                        this._LastIDX.WriteFullFence(0);
                                                        break;
                                                    }
                                                    else
                                                    {
                                                        this.Log($"Debug: {Invalid} line(s) in log file '{line}'");
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                            if (OldFile)
                            {
                                //rename it to keep it out of our way next time
                                try
                                {
                                    this.Log($"Debug: File was in the old log format, renaming to OLDLOGFORMAT. {NewFilename}");
                                    File.Move(NewFilename, NewFilename + ".OLDLOGFORMAT");
                                }
                                catch (Exception ex)
                                {
                                    this.Log($"Error: While renaming log to OLDLOGFORMAT, got: {ex.Message}");
                                }
                            }
                        }
                    }
                    else
                    {
                        this.Log($"Error: Gave up waiting for exclusive file access after {result.TimeMS}ms with {result.ErrRetryCnt} retries for {Filename}");
                    }

                    if (Directory.Exists(ExtractZipPath))
                    {
                        Directory.Delete(ExtractZipPath, true);
                    }
                }
                catch (Exception ex)
                {
                    this.Log($"Error: {Global.ExMsg(ex)}");
                }
            }

            if (Import)
            {
                this.Enabled = true;  //enable after we import
            }
            Global.UpdateProgressBar($"", 0, 0, 0);

            this.LastLoadTimeMS = sw.ElapsedMilliseconds;

            return(ret);
        }
Beispiel #3
0
        public bool MigrateHistoryCSV(string Filename)
        {
            using var Trace = new Trace();  //This c# 8.0 using feature will auto dispose when the function is done.

            bool ret = false;

            lock (DBLock)
            {
                try
                {
                    //if (!await this.IsSQLiteDBConnectedAsync())
                    //    await this.CreateConnectionAsync();

                    //run in another thread so we dont block UI
                    //await Task.Run(async () =>
                    //{

                    if (System.IO.File.Exists(Filename))
                    {
                        Global.UpdateProgressBar("Migrating history.csv...", 1, 1, 1);

                        Log($"Debug: Migrating history list from {Filename} ...");

                        Stopwatch SW = Stopwatch.StartNew();

                        //delete obsolete entries from history.csv
                        //CleanCSVList(); //removed to load the history list faster

                        List <string> result = new List <string>(); //List that later on will be containing all lines of the csv file

                        Global.WaitFileAccessResult wresult = Global.WaitForFileAccess(Filename);

                        if (wresult.Success)
                        {
                            //load all lines except the first line into List (the first line is the table heading and not an alert entry)
                            foreach (string line in System.IO.File.ReadAllLines(Filename).Skip(1))
                            {
                                result.Add(line);
                            }

                            Log($"...Found {result.Count} lines.");

                            List <string> itemsToDelete = new List <string>(); //stores all filenames of history.csv entries that need to be removed

                            //load all List elements into the ListView for each row
                            int added   = 0;
                            int removed = 0;
                            int cnt     = 0;
                            foreach (var val in result)
                            {
                                cnt++;

                                //if (cnt == 1 || cnt == result.Count || (cnt % (result.Count / 10) > 0))
                                //{
                                //    Global.UpdateProgressBar("Migrating history.csv...", cnt, 1, result.Count);
                                //}

                                History hist = new History().CreateFromCSV(val);
                                if (File.Exists(hist.Filename))
                                {
                                    if (this.InsertHistoryItem(hist))
                                    {
                                        added++;
                                        //this.AddedCount.AtomicIncrementAndGet();
                                    }
                                    else
                                    {
                                        removed++;
                                    }
                                }
                                else
                                {
                                    removed++;
                                }
                            }


                            ret = (added > 0);

                            //this.AddedCount.AtomicAddAndGet(added);
                            //this.DeletedCount.AtomicAddAndGet(removed);

                            //try to get a better feel how much time this function consumes - Vorlon
                            Log($"Debug: ...Added {added} out of {result.Count} history items ({removed} removed) in {SW.ElapsedMilliseconds}ms, {this.HistoryDic.Count} lines.");
                        }
                        else
                        {
                            Log($"Error: Could not gain access to history file for {wresult.TimeMS}ms with {wresult.ErrRetryCnt} retries - {AppSettings.Settings.HistoryFileName}");
                        }
                    }
                    else
                    {
                        Log($"Debug: Old history file does not exist, could not migrate: {Filename}");
                    }


                    //});
                }
                catch (Exception ex)
                {
                    Log("Error: " + Global.ExMsg(ex));
                }
            }

            Global.UpdateProgressBar("", 0, 0, 0);


            return(ret);
        }
Beispiel #4
0
        public bool CopyFileTo(string outputFilePath)
        {
            using var Trace = new Trace();  //This c# 8.0 using feature will auto dispose when the function is done.

            bool ret = false;

            int    bufferSize = 1024 * 1024;
            string copydir    = "";

            try
            {
                if (!outputFilePath.Contains("\\"))
                {
                    AITOOL.Log($"Error: Must specify a full path: {outputFilePath}");
                    return(false);
                }

                if (this.IsValid())  //loads into memory if not already loaded
                {
                    copydir = Path.GetDirectoryName(outputFilePath);

                    DirectoryInfo d = new DirectoryInfo(copydir);
                    if (d.Root != null && !d.Exists)
                    {
                        //dont try to create if working off root drive
                        d.Create();
                    }


                    //If the destination file exists, wait for exclusive access
                    Global.WaitFileAccessResult result2 = new Global.WaitFileAccessResult();
                    if (File.Exists(outputFilePath))
                    {
                        result2 = Global.WaitForFileAccess(outputFilePath, FileAccess.ReadWrite, FileShare.None, 3000, MinFileSize: 0);
                        if (result2.Success)
                        {
                            File.Delete(outputFilePath);
                        }
                    }
                    else
                    {
                        result2.Success = true;
                    }

                    if (result2.Success)
                    {
                        Stream inStream = this.ToStream();

                        using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                        {
                            fileStream.SetLength(inStream.Length);
                            int    bytesRead = -1;
                            byte[] bytes     = new byte[bufferSize];

                            while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0)
                            {
                                fileStream.Write(bytes, 0, bytesRead);
                            }
                        }

                        //wait for a small amount of time to allow the file to become accessible to blue iris - trying to prevent blank alert image in BI
                        //Thread.Sleep(50);

                        ret = true;
                    }
                    else
                    {
                        AITOOL.Log($"Error: Could not gain access to destination file ({result2.TimeMS}ms, '{result2.ResultString}') {outputFilePath}");
                    }
                }
                else
                {
                    AITOOL.Log($"Error: File not valid: {this.image_path}");
                }
            }
            catch (Exception ex)
            {
                AITOOL.Log($"Error: Copying to {outputFilePath}: {Global.ExMsg(ex)}");
            }

            return(ret);
        }
Beispiel #5
0
        public void LoadImage()
        {
            using var Trace = new Trace();  //This c# 8.0 using feature will auto dispose when the function is done.

            //since having a lot of trouble with image access problems, try to wait for image to become available, validate the image and load
            //a single time rather than multiple
            Global.WaitFileAccessResult result = new Global.WaitFileAccessResult();
            string LastError = "";

            this._valid = false;
            bool validate = !this._Temp;

            try
            {
                if (!string.IsNullOrEmpty(this.image_path) && File.Exists(this.image_path))
                {
                    Stopwatch sw = Stopwatch.StartNew();
                    do
                    {
                        int MaxWaitMS  = 0;
                        int MaxRetries = 0;
                        if (this._Temp)
                        {
                            MaxWaitMS  = 500;
                            MaxRetries = 2;
                        }
                        else
                        {
                            MaxWaitMS  = 10000;
                            MaxRetries = 100;
                        }

                        result = Global.WaitForFileAccess(this.image_path, FileAccess.Read, FileShare.None, MaxWaitMS, AppSettings.Settings.file_access_delay_ms, true, 4096, MaxRetries);

                        this.FileLockMS           = sw.ElapsedMilliseconds;
                        this.FileLockErrRetryCnt += result.ErrRetryCnt;

                        if (result.Success)
                        {
                            try
                            {
                                sw.Restart();
                                // Open a FileStream object using the passed in safe file handle.
                                using (FileStream fileStream = new FileStream(result.Handle, FileAccess.Read))
                                {
                                    using System.Drawing.Image img = System.Drawing.Image.FromStream(fileStream, true, validate);

                                    this._valid = img != null && img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) && img.Width > 0 && img.Height > 0;

                                    this.FileLoadMS = sw.ElapsedMilliseconds;

                                    if (!this._valid)
                                    {
                                        LastError = $"Error: Image file is not jpeg? {this.image_path}";
                                        AITOOL.Log(LastError);
                                        break;
                                    }
                                    else
                                    {
                                        this.Width  = img.Width;
                                        this.Height = img.Height;
                                        this.DPI    = img.HorizontalResolution;
                                        if (this._Temp)
                                        {
                                            this.FileLoadMS = sw.ElapsedMilliseconds;
                                        }
                                        else
                                        {
                                            using MemoryStream ms = new MemoryStream();
                                            //fileStream.CopyTo(ms);
                                            img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                                            this.ImageByteArray = ms.ToArray();
                                            this.FileSize       = this.ImageByteArray.Length;
                                            this.FileLoadMS     = sw.ElapsedMilliseconds;
                                            AITOOL.Log($"Debug: Image file is valid. Resolution={this.Width}x{this.Height}, LockMS={this.FileLockMS}ms (max={MaxWaitMS}ms), retries={this.FileLockErrRetryCnt}, size={Global.FormatBytes(ms.Length)}: {Path.GetFileName(this.image_path)}");
                                        }
                                        break;
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                this._valid = false;
                                LastError   = $"Error: Image is corrupt. LockMS={this.FileLockMS}ms (max={MaxWaitMS}ms), retries={this.FileLockErrRetryCnt}: {Path.GetFileName(this.image_path)} - {Global.ExMsg(ex)}";
                            }
                            finally
                            {
                                this._loaded = true;

                                if (!result.Handle.IsClosed)
                                {
                                    result.Handle.Close();
                                    result.Handle.Dispose();
                                }
                            }
                        }
                        else
                        {
                            if (this._Temp)
                            {
                                LastError = $"Debug: Could not gain access to the image in {result.TimeMS}ms, retries={result.ErrRetryCnt}: {Path.GetFileName(this.image_path)}";
                            }
                            else
                            {
                                LastError = $"Error: Could not gain access to the image in {result.TimeMS}ms, retries={result.ErrRetryCnt}: {Path.GetFileName(this.image_path)}";
                            }
                        }

                        if (this._Temp) //only one loop
                        {
                            break;
                        }
                    } while ((!result.Success || !this._valid) && sw.ElapsedMilliseconds < 30000);
                }
                else
                {
                    //AITOOL.Log("Error: Tried to load the image too soon?");
                }
            }
            catch (Exception ex)
            {
                AITOOL.Log($"Error: {Global.ExMsg(ex)}");
            }
            finally
            {
                if (result.Handle != null && !result.Handle.IsInvalid && !result.Handle.IsClosed)
                {
                    result.Handle.Close();
                    result.Handle.Dispose();
                }
                if (!this._valid && !string.IsNullOrEmpty(LastError))
                {
                    AITOOL.Log(LastError);
                }
            }
        }
Beispiel #6
0
        public void LoadImage()
        {
            //since having a lot of trouble with image access problems, try to wait for image to become available, validate the image and load
            //a single time rather than multiple
            Global.WaitFileAccessResult result = new Global.WaitFileAccessResult();
            string LastError = "";

            this._valid = false;
            try
            {
                if (!string.IsNullOrEmpty(this.image_path) && File.Exists(this.image_path))
                {
                    Stopwatch sw = Stopwatch.StartNew();
                    do
                    {
                        result = Global.WaitForFileAccess(this.image_path, FileAccess.Read, FileShare.None, 10000, 50, true, 4096);

                        this.FileLockMS           = sw.ElapsedMilliseconds;
                        this.FileLockErrRetryCnt += result.ErrRetryCnt;

                        if (result.Success)
                        {
                            try
                            {
                                sw.Restart();
                                // Open a FileStream object using the passed in safe file handle.
                                using (FileStream fileStream = new FileStream(result.Handle, FileAccess.Read))
                                {
                                    using System.Drawing.Image img = System.Drawing.Image.FromStream(fileStream, true, true);

                                    this._valid = img != null && img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg);

                                    this.FileLoadMS = sw.ElapsedMilliseconds;

                                    if (!this._valid)
                                    {
                                        LastError = $"Error: Image file is not jpeg? LockMS={this.FileLockMS}ms, retries={this.FileLockErrRetryCnt} - ({img.RawFormat}): {this.image_path}";
                                        AITOOL.Log(LastError);
                                        break;
                                    }
                                    else
                                    {
                                        this.Width            = img.Width;
                                        this.Height           = img.Height;
                                        using MemoryStream ms = new MemoryStream();
                                        //fileStream.CopyTo(ms);
                                        img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                                        this.ImageByteArray = ms.ToArray();
                                        this.FileLoadMS     = sw.ElapsedMilliseconds;
                                        AITOOL.Log($"Debug: Image file is valid. LockMS={this.FileLockMS}ms, retries={this.FileLockErrRetryCnt}, size={Global.FormatBytes(ms.Length)}: {Path.GetFileName(this.image_path)}");
                                        break;
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                this._valid = false;
                                LastError   = $"Error: Image is corrupt. LockMS={this.FileLockMS}ms, retries={this.FileLockErrRetryCnt}: {Global.ExMsg(ex)}";
                            }
                            finally
                            {
                                this._loaded = true;

                                if (!result.Handle.IsClosed)
                                {
                                    result.Handle.Close();
                                    result.Handle.Dispose();
                                }
                            }
                        }
                        else
                        {
                            LastError = $"Error: Could not gain access to the image in {result.TimeMS}ms, retries={result.ErrRetryCnt}.";
                        }
                    } while ((!result.Success || !this._valid) && sw.ElapsedMilliseconds < 30000);
                }
                else
                {
                    //AITOOL.Log("Error: Tried to load the image too soon?");
                }
            }
            catch (Exception ex)
            {
                AITOOL.Log($"Error: {Global.ExMsg(ex)}");
            }
            finally
            {
                if (result.Handle != null && !result.Handle.IsInvalid && !result.Handle.IsClosed)
                {
                    result.Handle.Close();
                    result.Handle.Dispose();
                }
                if (!this._valid && !string.IsNullOrEmpty(LastError))
                {
                    AITOOL.Log(LastError);
                }
            }
        }
Beispiel #7
0
      public async Task <string> MergeImageAnnotations(ClsTriggerActionQueueItem AQI)
      {
          int    countr               = 0;
          string detections           = "";
          string lasttext             = "";
          string lastposition         = "";
          string OutputImageFile      = "";
          bool   bSendTelegramMessage = false;

          try
          {
              Global.LogMessage("Merging image annotations: " + AQI.CurImg.image_path);

              if (System.IO.File.Exists(AQI.CurImg.image_path))
              {
                  Stopwatch sw = Stopwatch.StartNew();

                  using (Bitmap img = new Bitmap(AQI.CurImg.image_path))
                  {
                      using (Graphics g = Graphics.FromImage(img))
                      {
                          g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                          g.SmoothingMode     = SmoothingMode.HighQuality;
                          g.PixelOffsetMode   = PixelOffsetMode.HighQuality;
                          //http://csharphelper.com/blog/2014/09/understand-font-aliasing-issues-in-c/
                          g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;


                          System.Drawing.Color color = new System.Drawing.Color();

                          if (AQI.Hist != null && !string.IsNullOrEmpty(AQI.Hist.PredictionsJSON))
                          {
                              System.Drawing.Rectangle rect;
                              System.Drawing.SizeF     size;
                              Brush rectBrush;
                              Color boxColor;

                              List <ClsPrediction> predictions = new List <ClsPrediction>();

                              predictions = Global.SetJSONString <List <ClsPrediction> >(AQI.Hist.PredictionsJSON);

                              foreach (var pred in predictions)
                              {
                                  bool Merge = false;

                                  if (AppSettings.Settings.HistoryOnlyDisplayRelevantObjects && pred.Result == ResultType.Relevant)
                                  {
                                      Merge = true;
                                  }
                                  else if (!AppSettings.Settings.HistoryOnlyDisplayRelevantObjects)
                                  {
                                      Merge = true;
                                  }

                                  if (Merge)
                                  {
                                      lasttext = pred.ToString();
                                      //lasttext = $"{cam.last_detections[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, AQI.cam.last_confidences[i] * 100)}";

                                      double xmin = pred.XMin + AQI.cam.XOffset;
                                      double ymin = pred.YMin + AQI.cam.YOffset;
                                      double xmax = pred.XMax;
                                      double ymax = pred.YMax;

                                      if (AQI.cam.telegram_mask_enabled && !bSendTelegramMessage)
                                      {
                                          bSendTelegramMessage ^= this.TelegramOutsideMask(AQI.cam.Name, xmin, xmax, ymin, ymax, img.Width, img.Height);
                                      }

                                      int penSize = 2;
                                      if (img.Height > 1200)
                                      {
                                          penSize = 4;
                                      }
                                      else if (img.Height >= 800 && img.Height <= 1200)
                                      {
                                          penSize = 3;
                                      }

                                      boxColor = Color.FromArgb(150, this.GetBoxColor(AQI.cam.last_detections[countr]));
                                      rect     = new System.Drawing.Rectangle(xmin.ToInt(), ymin.ToInt(), xmax.ToInt() - xmin.ToInt(), ymax.ToInt() - ymin.ToInt());
                                      using (Pen pen = new Pen(boxColor, penSize)) { g.DrawRectangle(pen, rect); }   //draw rectangle

                                      // Text Color
                                      Brush textColor = (boxColor.GetBrightness() > 0.5 ? Brushes.Black : Brushes.White);

                                      float fontSize = AppSettings.Settings.RectDetectionTextSize * ((float)img.Height / 1080);   // Scale for image sizes
                                      if (fontSize < 8)
                                      {
                                          fontSize = 8;
                                      }
                                      Font textFont = new Font(AppSettings.Settings.RectDetectionTextFont, fontSize);

                                      //object name text below rectangle
                                      rect      = new System.Drawing.Rectangle(xmin.ToInt() - 1, ymax.ToInt(), (int)img.Width, (int)img.Height); //sets bounding box for drawn text
                                      rectBrush = new SolidBrush(boxColor);                                                                      //sets background rectangle color

                                      size = g.MeasureString(lasttext, textFont);                                                                //finds size of text to draw the background rectangle
                                      g.FillRectangle(rectBrush, xmin.ToInt() - 1, ymax.ToInt(), size.Width, size.Height);                       //draw background rectangle for detection text
                                      g.DrawString(lasttext, textFont, textColor, rect);                                                         //draw detection text

                                      g.Flush();
                                      countr++;
                                  }
                              }
                          }
                          else
                          {
                              //Use the old way -this code really doesnt need to be here but leaving just to make sure
                              detections = AQI.cam.last_detections_summary;
                              if (string.IsNullOrEmpty(detections))
                              {
                                  detections = "";
                              }

                              string label = Global.GetWordBetween(detections, "", ":");

                              if (label.Contains("irrelevant") || label.Contains("confidence") || label.Contains("masked") || label.Contains("errors"))
                              {
                                  detections = detections.Split(':')[1];   //removes the "1x masked, 3x irrelevant:" before the actual detection, otherwise this would be displayed in the detection tags

                                  if (label.Contains("masked"))
                                  {
                                      color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor);
                                  }
                                  else
                                  {
                                      color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor);
                                  }
                              }
                              else
                              {
                                  color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor);
                              }

                              //List<string> detectlist = Global.Split(detections, "|;");
                              countr = AQI.cam.last_detections.Count();

                              //display a rectangle around each relevant object


                              for (int i = 0; i < countr; i++)
                              {
                                  //({ Math.Round((user.confidence * 100), 2).ToString() }%)
                                  lasttext     = $"{AQI.cam.last_detections[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, AQI.cam.last_confidences[i])}";
                                  lastposition = AQI.cam.last_positions[i];    //load 'xmin,ymin,xmax,ymax' from third column into a string

                                  //store xmin, ymin, xmax, ymax in separate variables
                                  Int32.TryParse(lastposition.Split(',')[0], out int xmin);
                                  Int32.TryParse(lastposition.Split(',')[1], out int ymin);
                                  Int32.TryParse(lastposition.Split(',')[2], out int xmax);
                                  Int32.TryParse(lastposition.Split(',')[3], out int ymax);

                                  xmin = xmin + AQI.cam.XOffset;
                                  ymin = ymin + AQI.cam.YOffset;

                                  System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);


                                  using (Pen pen = new Pen(color, AppSettings.Settings.RectBorderWidth))
                                  {
                                      g.DrawRectangle(pen, rect);   //draw rectangle
                                  }

                                  //we need this since people can change the border width in the json file
                                  int halfbrd = AppSettings.Settings.RectBorderWidth / 2;

                                  //object name text below rectangle
                                  rect = new System.Drawing.Rectangle(xmin - halfbrd, ymax + halfbrd, img.Width, img.Height);   //sets bounding box for drawn text


                                  Brush brush = new SolidBrush(color);                                                                                                                     //sets background rectangle color

                                  System.Drawing.SizeF size = g.MeasureString(lasttext, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle
                                  g.FillRectangle(brush, xmin - halfbrd, ymax + halfbrd, size.Width, size.Height);                                                                         //draw grey background rectangle for detection text
                                  g.DrawString(lasttext, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize), Brushes.Black, rect);           //draw detection text

                                  g.Flush();

                                  //Global.LogMessage($"...{i}, LastText='{lasttext}' - LastPosition='{lastposition}'");
                              }
                          }


                          if (countr > 0)
                          {
                              GraphicsState gs = g.Save();

                              ImageCodecInfo jpgEncoder = this.GetImageEncoder(ImageFormat.Jpeg);

                              // Create an Encoder object based on the GUID
                              // for the Quality parameter category.
                              System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;

                              // Create an EncoderParameters object.
                              // An EncoderParameters object has an array of EncoderParameter
                              // objects. In this case, there is only one
                              // EncoderParameter object in the array.
                              EncoderParameters myEncoderParameters = new EncoderParameters(1);

                              EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, AQI.cam.Action_image_merge_jpegquality);    //100=least compression, largest file size, best quality
                              myEncoderParameters.Param[0] = myEncoderParameter;

                              Global.WaitFileAccessResult result = new Global.WaitFileAccessResult();
                              result.Success = true;   //assume true

                              if (AQI.cam.Action_image_merge_detections_makecopy)
                              {
                                  OutputImageFile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Path.GetFileName(AQI.CurImg.image_path));
                              }
                              else
                              {
                                  OutputImageFile = AQI.CurImg.image_path;
                              }

                              if (System.IO.File.Exists(OutputImageFile))
                              {
                                  result = await Global.WaitForFileAccessAsync(OutputImageFile, FileAccess.ReadWrite, FileShare.ReadWrite);
                              }

                              if (result.Success)
                              {
                                  img.Save(OutputImageFile, jpgEncoder, myEncoderParameters);
                                  if (AQI.cam.telegram_mask_enabled && bSendTelegramMessage)
                                  {
                                      string telegram_file = "temp\\" + Path.GetFileName(OutputImageFile).Insert((Path.GetFileName(OutputImageFile).Length - 4), "_telegram");
                                      img.Save(telegram_file, jpgEncoder, myEncoderParameters);
                                  }
                                  Log($"Debug: Merged {countr} detections in {sw.ElapsedMilliseconds}ms into image {OutputImageFile}");
                              }
                              else
                              {
                                  Log($"Error: Could not gain access to write merged file {OutputImageFile}");
                              }
                          }
                          else
                          {
                              Log($"Debug: No detections to merge.  Time={sw.ElapsedMilliseconds}ms, {OutputImageFile}");
                          }
                      }
                  }
              }
              else
              {
                  Global.LogMessage("Error: could not find last image with detections: " + AQI.CurImg.image_path);
              }
          }
          catch (Exception ex)
          {
              Global.LogMessage($"Error: Detections='{detections}', LastText='{lasttext}', LastPostions='{lastposition}' - " + Global.ExMsg(ex));
          }

          return(OutputImageFile);
      }