Skip to content

Commit

Permalink
* Monkeying with dynamic masking ObjectPosition.Equals so it stores c…
Browse files Browse the repository at this point in the history
…olumns for the last variances checked for troubleshooting, and so they are stored as whole number percentages. Lets see if this helps figure out why sometimes distant/small detections need such a large variance percent set to be considered equal.

* Added Camera.Action_image_merge_jpegquality = 80 so you can control how large resulting images are.  100=largest, best quality, 20=small, low quality, small image.
* Changed a 'warning' log entry in DynamicMaskDetails to INFO so users wont get telegram warnings for it.
* Increase cleanhistorytimer from 1 minute to 5 minutes - perhaps less chance of threading conflicts
* Fix bug where mask history objects would not always be removed.  Changed TimeSpan.Minutes to TimeSpan.TotalMinutes in CleanUpExpiredHistory.
* Moved more functions from the UI (shell.cs) to its own class (aitool.cs > InitializeBackend) in prep for creating true service
  • Loading branch information
VorlonCD committed Sep 17, 2020
1 parent a2a0cae commit efee295
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 139 deletions.
115 changes: 114 additions & 1 deletion src/UI/AITOOL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
using Arch.CMessaging.Client.Core.Utils;
using Telegram.Bot.Exceptions;
using SixLabors.ImageSharp.Processing;
using System.Reflection;

namespace AITool
{
Expand Down Expand Up @@ -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<ClsURLItem> WaitForNextURL()
{

Expand Down Expand Up @@ -227,7 +336,7 @@ public static async Task<ClsURLItem> 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;
Expand All @@ -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)
Expand Down
30 changes: 16 additions & 14 deletions src/UI/Camera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}");
Expand Down
8 changes: 4 additions & 4 deletions src/UI/Frm_DynamicMaskDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

}

Expand Down Expand Up @@ -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
{
Expand Down
6 changes: 3 additions & 3 deletions src/UI/MaskManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}
Expand Down
56 changes: 44 additions & 12 deletions src/UI/ObjectPosition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace AITool
public class ObjectPosition:IEquatable<ObjectPosition>
{
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; }
Expand All @@ -27,9 +27,27 @@ public class ObjectPosition:IEquatable<ObjectPosition>
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;
Expand All @@ -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()
Expand Down
Loading

0 comments on commit efee295

Please sign in to comment.