diff --git a/src/UI/AITOOL.cs b/src/UI/AITOOL.cs index ae10032e..27df2f67 100644 --- a/src/UI/AITOOL.cs +++ b/src/UI/AITOOL.cs @@ -37,6 +37,7 @@ using Arch.CMessaging.Client.Core.Utils; using Telegram.Bot.Exceptions; using SixLabors.ImageSharp.Processing; +using System.Reflection; namespace AITool { @@ -81,6 +82,114 @@ public static class AITOOL public static DateTime last_telegram_trigger_time = DateTime.MinValue; public static DateTime TelegramRetryTime = DateTime.MinValue; + public static void InitializeBackend() + { + + try + { + + //initialize the log and history file writers - log entries will be queued for fast file logging performance AND if the file + //is locked for any reason, it will wait in the queue until it can be written + //The logwriter will also rotate out log files (each day, rename as log_date.txt) and delete files older than 60 days + LogWriter = new LogFileWriter(AppSettings.Settings.LogFileName); + HistoryWriter = new LogFileWriter(AppSettings.Settings.HistoryFileName); + + //if log file does not exist, create it - this used to be in LOG function but doesnt need to be checked everytime log written to + if (!System.IO.File.Exists(AppSettings.Settings.LogFileName)) + { + //the logwriter auto creates the file if needed + LogWriter.WriteToLog("Log format: [dd.MM.yyyy, HH:mm:ss]: Log text.", true); + + } + + //load settings + AppSettings.Load(); + + LogWriter.MaxLogFileAgeDays = AppSettings.Settings.MaxLogFileAgeDays; + LogWriter.MaxLogSize = AppSettings.Settings.MaxLogFileSize; + + HistoryWriter.MaxLogFileAgeDays = AppSettings.Settings.MaxLogFileAgeDays; + HistoryWriter.MaxLogSize = AppSettings.Settings.MaxLogFileSize; + + Assembly CurAssm = Assembly.GetExecutingAssembly(); + string AssemNam = CurAssm.GetName().Name; + string AssemVer = CurAssm.GetName().Version.ToString(); + + Log(""); + Log(""); + Log($"Starting {AssemNam} Version {AssemVer} built on {Global.RetrieveLinkerTimestamp()}"); + if (AppSettings.AlreadyRunning) + { + Log("*** Another instance is already running *** "); + Log(" --- Files will not be monitored from within this session "); + Log(" --- Log tab will not display output from service instance. You will need to directly open log file for that "); + Log(" --- Changes made here to settings will require that you stop/start the service "); + Log(" --- You must close/reopen app to see NEW history items/detections"); + } + if (Global.IsAdministrator()) + { + Log("*** Running as administrator ***"); + } + else + { + Log("Not running as administrator."); + } + + if (AppSettings.Settings.SettingsFileName.ToLower().StartsWith(Directory.GetCurrentDirectory().ToLower())) + { + Log($"*** Start in/current directory is the same as where the EXE is running from: {Directory.GetCurrentDirectory()} ***"); + } + else + { + string msg = $"Error: The Start in/current directory is NOT the same as where the EXE is running from: \r\n{Directory.GetCurrentDirectory()}\r\n{AppDomain.CurrentDomain.BaseDirectory}"; + Log(msg); + } + + //initialize blueiris info class to get camera names, clip paths, etc + BlueIrisInfo = new BlueIris(); + if (BlueIrisInfo.IsValid) + { + Log($"BlueIris path is '{BlueIrisInfo.AppPath}', with {BlueIrisInfo.Cameras.Count()} cameras and {BlueIrisInfo.ClipPaths.Count()} clip folder paths configured."); + } + else + { + Log($"BlueIris not detected."); + } + + //if camera settings folder does not exist, create it + if (!Directory.Exists("./cameras/")) + { + //create folder + DirectoryInfo di = Directory.CreateDirectory("./cameras"); + Log("./cameras/" + " dir created."); + } + + //check if history.csv exists, if not then create it + if (!System.IO.File.Exists(AppSettings.Settings.HistoryFileName)) + { + Log("ATTENTION: Creating database cameras/history.csv ."); + HistoryWriter.WriteToLog("filename|date and time|camera|detections|positions of detections|success", true); + } + + //initialize the deepstack class - it collects info from running deepstack processes, detects install location, and + //allows for stopping and starting of its service + DeepStackServerControl = new DeepStack(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port); + + UpdateWatchers(); + + //Start the thread that watches for the file queue + Task.Run(ImageQueueLoop); + + + } + catch (Exception ex) + { + + Global.Log("Error: " + Global.ExMsg(ex)); + } + + } + public static async Task WaitForNextURL() { @@ -227,7 +336,7 @@ public static async Task WaitForNextURL() break; } - if ((DateTime.Now - LastWaitingLog).Minutes >= 10) + if ((DateTime.Now - LastWaitingLog).TotalMinutes >= 10) { Log("---- All URL's are in use or disabled, waiting..."); LastWaitingLog = DateTime.Now; @@ -253,6 +362,10 @@ public static async Task ImageQueueLoop() DateTime LastCleanDupesTime = DateTime.MinValue; + + //lets wait 5 seconds to let the UI settle down a bit + await Task.Delay(5000); + //Start infinite loop waiting for images to come into queue while (true) diff --git a/src/UI/Camera.cs b/src/UI/Camera.cs index 22f8b985..020549dc 100644 --- a/src/UI/Camera.cs +++ b/src/UI/Camera.cs @@ -56,6 +56,7 @@ public class Camera public bool Action_image_copy_enabled = false; public bool Action_image_merge_detections = false; + public long Action_image_merge_jpegquality = 80; public string Action_network_folder = ""; public string Action_network_folder_filename = "[ImageFilenameNoExt]"; public bool Action_RunProgram = false; @@ -188,25 +189,26 @@ public void MergeImageAnnotations(string OutputImageFile, ClsImageQueueItem CurI Global.Log($"...{i}, LastText='{lasttext}' - LastPosition='{lastposition}'"); } - GraphicsState gs = g.Save(); + if (countr > 0) + { - ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg); + GraphicsState gs = g.Save(); - // Create an Encoder object based on the GUID - // for the Quality parameter category. - System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality; + ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg); - // 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); + // Create an Encoder object based on the GUID + // for the Quality parameter category. + System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality; - EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 90L); //100=least compression, largest file size, best quality - myEncoderParameters.Param[0] = myEncoderParameter; + // 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, this.Action_image_merge_jpegquality); //100=least compression, largest file size, best quality + myEncoderParameters.Param[0] = myEncoderParameter; - if (countr > 0) - { img.Save(OutputImageFile, jpgEncoder, myEncoderParameters); Global.Log($"Merged {countr} detections in {sw.ElapsedMilliseconds}ms into image {OutputImageFile}"); diff --git a/src/UI/Frm_DynamicMaskDetails.cs b/src/UI/Frm_DynamicMaskDetails.cs index 16467f33..a9fd0b53 100644 --- a/src/UI/Frm_DynamicMaskDetails.cs +++ b/src/UI/Frm_DynamicMaskDetails.cs @@ -40,12 +40,12 @@ public string GetBestImage() } else { - Global.Log("WARNING: Mask image file not found at location: " + contextMenuPosObj.imagePath + ". Defaulting to last processed image"); + Global.Log("INFO: Mask image file not found at location: " + contextMenuPosObj.imagePath + ". Defaulting to last processed image"); } } else { - Global.Log("WARNING: Mask image file path was blank or NULL. Defaulting to last processed image"); + Global.Log("INFO: Mask image file path was blank or NULL. Defaulting to last processed image"); } @@ -393,9 +393,9 @@ private void ShowImageMask(PaintEventArgs e = null) Brush brush = new SolidBrush(color); //sets background rectangle color - System.Drawing.SizeF size = e.Graphics.MeasureString(op.label, new Font("Segoe UI Semibold", 10)); //finds size of text to draw the background rectangle + System.Drawing.SizeF size = e.Graphics.MeasureString(op.label, new Font("Segoe UI Semibold", AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle e.Graphics.FillRectangle(brush, xmin - 1, ymax, size.Width, size.Height); //draw grey background rectangle for detection text - e.Graphics.DrawString(op.label, new Font("Segoe UI Semibold", 10), Brushes.Black, rect); //draw detection text + e.Graphics.DrawString(op.label, new Font("Segoe UI Semibold", AppSettings.Settings.RectDetectionTextSize), Brushes.Black, rect); //draw detection text } else { diff --git a/src/UI/MaskManager.cs b/src/UI/MaskManager.cs index 2ce54185..9d43cad0 100644 --- a/src/UI/MaskManager.cs +++ b/src/UI/MaskManager.cs @@ -44,7 +44,7 @@ public MaskManager() //register event handler to run clean history every minute cleanHistoryTimer.Elapsed += new System.Timers.ElapsedEventHandler(cleanHistoryEvent); - cleanHistoryTimer.Interval = 60000; // 1min = 60,000ms + cleanHistoryTimer.Interval = 300000; // 5min = 300,000ms //Adding this here too since have seen several mysterious cases (including my machine) where history not cleaned up if (_masking_enabled) cleanHistoryTimer.Start(); @@ -235,12 +235,12 @@ private void CleanUpExpiredHistory() { ObjectPosition historyObject = last_positions_history[x]; TimeSpan ts = DateTime.Now - historyObject.createDate; - int minutes = ts.Minutes; + double minutes = ts.TotalMinutes; //Global.Log("\t" + historyObject.ToString() + " existed for: " + ts.Minutes + " minutes"); if (minutes >= history_save_mins) { - Global.Log("Removing expired history: " + historyObject.ToString() + " which existed for " + ts.Minutes + " minutes."); + Global.Log($"Removing expired history: {historyObject.ToString()} which existed for {minutes.ToString("#######0.0")} minutes. (max={history_save_mins})"); last_positions_history.RemoveAt(x); } } diff --git a/src/UI/ObjectPosition.cs b/src/UI/ObjectPosition.cs index 2a90d693..a8cfe806 100644 --- a/src/UI/ObjectPosition.cs +++ b/src/UI/ObjectPosition.cs @@ -7,7 +7,7 @@ namespace AITool public class ObjectPosition:IEquatable { public string label { get; } = ""; - public DateTime createDate { get; set; } + public DateTime createDate { get; set; } = DateTime.MinValue; public DateTime LastSeenDate { get; set; } = DateTime.MinValue; public int counter { get; set; } @@ -27,9 +27,27 @@ public class ObjectPosition:IEquatable public string cameraName { get; set; } = ""; public string imagePath { get; set; } = ""; + //store the calcs for troubleshooting + public bool LastMatched { get; set; } = false; + public double LastWidthPercentVariance { get; set; } = 0; + public double LastHeightPercentVariance { get; set; } = 0; + + //calculate percentage change in starting corner of the x,y axis (upper-left corner of the rectangle) + public double LastxPercentVariance { get; set; } = 0; + public double LastyPercentVariance { get; set; } = 0; + public int LastCompared_xmin { get; set; } = 0; + public int LastCompared_ymin { get; set; } = 0; + public int LastCompared_xmax { get; set; } = 0; + public int LastCompared_ymax { get; set; } = 0; + public int LastCompared_width { get; set; } = 0; + public int LastCompared_height { get; set; } = 0; + public ObjectPosition(int xmin, int ymin, int xmax, int ymax, string label, int imageWidth, int imageHeight, string cameraName, string imagePath) { - createDate = DateTime.Now; + //not sure if the json deserialize is messing with createdate? Trying to track down history not being deleted issue + if (this.createDate == DateTime.MinValue) + this.createDate = DateTime.Now; + this.cameraName = cameraName; this.imagePath = imagePath; this.label = label; @@ -56,22 +74,36 @@ public override bool Equals(object obj) public bool Equals(ObjectPosition other) { + + if (other == null) + return false; + + this.LastCompared_height = other.height; + this.LastCompared_width = other.width; + + this.LastCompared_xmax = other.xmax; + this.LastCompared_ymax = other.ymax; + this.LastCompared_xmin = other.xmin; + this.LastCompared_ymin = other.ymin; + //calculate the percentage variance for width and height of selected object - double widthVariance = (double)Math.Abs(this.width - other.width) / this.width; - double heightVariance = (double)Math.Abs(this.height - other.height) / this.height; + this.LastWidthPercentVariance = (double)Math.Abs(this.width - other.width) / this.width * 100; + this.LastHeightPercentVariance = (double)Math.Abs(this.height - other.height) / this.height * 100; //calculate percentage change in starting corner of the x,y axis (upper-left corner of the rectangle) - double xPercentVariance = (double)(Math.Abs(this.xmin - other.xmin)) / imageWidth; - double yPercentVariance = (double)(Math.Abs(this.ymin - other.ymin)) / imageHeight; + this.LastxPercentVariance = (double)(Math.Abs(this.xmin - other.xmin)) / imageWidth * 100; + this.LastyPercentVariance = (double)(Math.Abs(this.ymin - other.ymin)) / imageHeight * 100; //convert whole number to percent - double percent = thresholdPercent / 100; + double percent = thresholdPercent; /// 100; + + this.LastMatched = (this.LastxPercentVariance <= percent) && + (this.LastyPercentVariance <= percent) && + (this.LastWidthPercentVariance <= percent) && + (this.LastHeightPercentVariance <= percent); + + return this.LastMatched; - return (other != null && - (xPercentVariance <= percent) && - (yPercentVariance <= percent) && - (widthVariance <= percent) && - (heightVariance <= percent)); } public override int GetHashCode() diff --git a/src/UI/Shell.cs b/src/UI/Shell.cs index c93074a4..0445d40b 100644 --- a/src/UI/Shell.cs +++ b/src/UI/Shell.cs @@ -51,73 +51,24 @@ public Shell() //Initialize the rich text log window writer. You can use any 'color' name in your log text //for example {red}Error!{white}. Note if you use $ for the string, you have use two brackets like this: {{red}} RTFLogger = new RichTextBoxEx(RTF_Log, true); - //initialize the log and history file writers - log entries will be queued for fast file logging performance AND if the file - //is locked for any reason, it will wait in the queue until it can be written - //The logwriter will also rotate out log files (each day, rename as log_date.txt) and delete files older than 60 days - LogWriter = new LogFileWriter(AppSettings.Settings.LogFileName); - HistoryWriter = new LogFileWriter(AppSettings.Settings.HistoryFileName); - - //if log file does not exist, create it - this used to be in LOG function but doesnt need to be checked everytime log written to - if (!System.IO.File.Exists(AppSettings.Settings.LogFileName)) - { - //the logwriter auto creates the file if needed - LogWriter.WriteToLog("Log format: [dd.MM.yyyy, HH:mm:ss]: Log text.", true); - - } - - //load settings - AppSettings.Load(); - - LogWriter.MaxLogFileAgeDays = AppSettings.Settings.MaxLogFileAgeDays; - LogWriter.MaxLogSize = AppSettings.Settings.MaxLogFileSize; - - HistoryWriter.MaxLogFileAgeDays = AppSettings.Settings.MaxLogFileAgeDays; - HistoryWriter.MaxLogSize = AppSettings.Settings.MaxLogFileSize; - RTFLogger.AutoScroll.WriteFullFence(AppSettings.Settings.Autoscroll_log); + InitializeBackend(); + string AssemVer = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - Log(""); - Log(""); lbl_version.Text = $"Version {AssemVer} built on {Global.RetrieveLinkerTimestamp()}"; - Log($"Starting {Application.ProductName} {lbl_version.Text}"); - if (AppSettings.AlreadyRunning) - { - Log("*** Another instance is already running *** "); - Log(" --- Files will not be monitored from within this session "); - Log(" --- Log tab will not display output from service instance. You will need to directly open log file for that "); - Log(" --- Changes made here to settings will require that you stop/start the service "); - Log(" --- You must close/reopen app to see NEW history items/detections"); - } - if (Global.IsAdministrator()) - { - Log("*** Running as administrator ***"); - } - else - { - Log("Not running as administrator."); - } - //initialize blueiris info class to get camera names, clip paths, etc - BlueIrisInfo = new BlueIris(); - if (BlueIrisInfo.IsValid) - { - Log($"BlueIris path is '{BlueIrisInfo.AppPath}', with {BlueIrisInfo.Cameras.Count()} cameras and {BlueIrisInfo.ClipPaths.Count()} clip folder paths configured."); - } - else + + + if (!AppSettings.Settings.SettingsFileName.ToLower().StartsWith(Directory.GetCurrentDirectory().ToLower()) ) { - Log($"BlueIris not detected."); + string msg = $"Error: The Start in/current directory is NOT the same as where the EXE is running from: \r\n{Directory.GetCurrentDirectory()}\r\n{AppDomain.CurrentDomain.BaseDirectory}"; + MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } + //--------------------------------------------------------------------------------------------------------- this.Resize += new System.EventHandler(this.Form1_Resize); //resize event to enable 'minimize to tray' - //if camera settings folder does not exist, create it - if (!Directory.Exists("./cameras/")) - { - //create folder - DirectoryInfo di = Directory.CreateDirectory("./cameras"); - Log("./cameras/" + " dir created."); - } //--------------------------------------------------------------------------- //CAMERAS TAB @@ -154,25 +105,6 @@ public Shell() list1.Columns[5].Width = list1.Width * 10 / 100; //checkmark if something relevant was detected or not list1.FullRowSelect = true; //make all columns clickable - //check if history.csv exists, if not then create it - if (!System.IO.File.Exists(AppSettings.Settings.HistoryFileName)) - { - Log("ATTENTION: Creating database cameras/history.csv ."); - try - { - HistoryWriter.WriteToLog("filename|date and time|camera|detections|positions of detections|success", true); - - //using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "cameras/history.csv")) - //{ - // sw.WriteLine("filename|date and time|camera|detections|positions of detections|success"); - //} - } - catch - { - lbl_errors.Text = "Can't create cameras/history.csv database!"; - } - - } //this method is slow if the database is large, so it's usually only called on startup. During runtime, DeleteListImage() is used to remove obsolete images from the history list @@ -181,12 +113,11 @@ public Shell() //load entries from history.csv into history ListView //LoadFromCSV(); not neccessary because below, comboBox_filter_camera.SelectedIndex will call LoadFromCSV() - splitContainer1.Panel2Collapsed = true; //collapse filter panel under left list + //splitContainer1.Panel2Collapsed = true; //collapse filter panel under left list comboBox_filter_camera.Items.Add("All Cameras"); //add "all cameras" entry in filter dropdown combobox comboBox_filter_camera.SelectedIndex = comboBox_filter_camera.FindStringExact("All Cameras"); //select all cameras entry - //--------------------------------------------------------------------------- //SETTINGS TAB @@ -222,10 +153,6 @@ public Shell() //Deepstack server TAB - //initialize the deepstack class - it collects info from running deepstack processes, detects install location, and - //allows for stopping and starting of its service - DeepStackServerControl = new DeepStack(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port); - if (!DeepStackServerControl.IsInstalled) { //remove deepstack tab if not installed @@ -242,13 +169,7 @@ public Shell() LoadDeepStackTab(true); } - //set httpclient timeout: - //client.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientTimeoutSeconds); - - UpdateWatchers(); - //Start the thread that watches for the file queue - Task.Run(ImageQueueLoop); this.Opacity = 1; diff --git a/src/UI/UI.csproj b/src/UI/UI.csproj index fbd18d84..9bca34ad 100644 --- a/src/UI/UI.csproj +++ b/src/UI/UI.csproj @@ -75,9 +75,8 @@ ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll False - - ..\packages\Microsoft.Data.Sqlite.Core.3.1.7\lib\netstandard2.0\Microsoft.Data.Sqlite.dll - False + + ..\packages\Microsoft.Data.Sqlite.Core.3.1.8\lib\netstandard2.0\Microsoft.Data.Sqlite.dll ..\packages\Microsoft.Extensions.Configuration.3.1.8\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll @@ -209,9 +208,9 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll False - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - False + + ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net461\System.Security.Cryptography.Algorithms.dll + True True @@ -224,9 +223,9 @@ False True - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - False + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll + True True @@ -413,7 +412,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/src/UI/packages.config b/src/UI/packages.config index 42e5a96b..f5c83cbb 100644 --- a/src/UI/packages.config +++ b/src/UI/packages.config @@ -2,8 +2,8 @@ - - + + @@ -14,11 +14,11 @@ - + - + @@ -36,10 +36,10 @@ - + - + \ No newline at end of file