Skip to content

Commit

Permalink
* Significantly reduced memory usage. It turns out I've always been m…
Browse files Browse the repository at this point in the history
…istakenly caching every single image processed in memory for at least an HOUR. Which can be a large amount of ram with multiple cameras, 4k, etc. #345
  • Loading branch information
VorlonCD committed Mar 31, 2024
1 parent a073f46 commit 0eb7d17
Show file tree
Hide file tree
Showing 15 changed files with 2,008 additions and 2,187 deletions.
33 changes: 15 additions & 18 deletions src/UI/AITOOL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static class AITOOL
public static object ImageLoopLockObject = new object();

//thread safe dictionary to prevent more than one file being processed at one time
public static ConcurrentDictionary<string, ClsImageQueueItem> detection_dictionary = new ConcurrentDictionary<string, ClsImageQueueItem>();
public static ConcurrentDictionary<string, DateTime> image_detection_dictionary = new ConcurrentDictionary<string, DateTime>();


public static Dictionary<string, ClsFileSystemWatcher> watchers = new Dictionary<string, ClsFileSystemWatcher>();
Expand Down Expand Up @@ -117,8 +117,7 @@ public static async Task InitializeBackend()
{
using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done.

Global.JSONContractResolver = new DefaultContractResolver();
Global.JSONContractResolver.NamingStrategy = new CamelCaseNamingStrategy();


//initialize log manager with basic settings so we can start getting output if needed
if (Global.IsService)
Expand Down Expand Up @@ -497,7 +496,7 @@ public static System.Drawing.Image CropImage(ClsImageQueueItem img, System.Drawi
try
{
DecoderOptions dc = new DecoderOptions();
using (SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(img.ImageByteArray)) //, out IImageFormat format))
using (SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(img.ToMemStream())) //, out IImageFormat format))
{
image.Mutate(i => i.Crop(SixLabors.ImageSharp.Rectangle.FromLTRB(cropArea.Left, cropArea.Top, cropArea.Right, cropArea.Bottom)));

Expand Down Expand Up @@ -1596,13 +1595,13 @@ public static async Task ImageQueueLoop()
if ((DateTime.Now - LastCleanDupesTime).TotalMinutes >= 60)
{
int cnt = 0;
foreach (KeyValuePair<string, ClsImageQueueItem> kvPair in detection_dictionary)
foreach (KeyValuePair<string, DateTime> kvPair in image_detection_dictionary)
{
if ((DateTime.Now - kvPair.Value.TimeAdded).TotalMinutes >= 30)
if ((DateTime.Now - kvPair.Value).TotalMinutes >= 30)
{ // Remove expired item.
cnt++;
ClsImageQueueItem removedItem;
detection_dictionary.TryRemove(kvPair.Key, out removedItem);
//ClsImageQueueItem removedItem;
image_detection_dictionary.TryRemove(kvPair.Key, out _);
}
}

Expand Down Expand Up @@ -1643,7 +1642,7 @@ public static void AddImageToQueue(string Filename)
try
{
//make sure we are not processing a duplicate file...
if (detection_dictionary.ContainsKey(Filename.ToLower()))
if (image_detection_dictionary.ContainsKey(Filename.ToLower()))
{
Log("Skipping image because of duplicate Created File Event: " + Filename);
}
Expand All @@ -1669,7 +1668,7 @@ public static void AddImageToQueue(string Filename)
Log("Debug: ");
Log($"Debug: ====================== Adding new image to queue (Count={ImageProcessQueue.Count + 1}): " + Filename, "", cam, Filename);
ClsImageQueueItem CurImg = new ClsImageQueueItem(Filename, qsize);
detection_dictionary.TryAdd(Filename.ToLower(), CurImg);
image_detection_dictionary.TryAdd(Filename.ToLower(), DateTime.Now);
ImageProcessQueue.Enqueue(CurImg);
scalc.AddToCalc(qsize);
Global.SendMessage(MessageType.ImageAddedToQueue);
Expand Down Expand Up @@ -2196,7 +2195,7 @@ public static async Task<ClsAIServerResponse> GetDetectionsFromAIServer(ClsImage

using MultipartFormDataContent request = new MultipartFormDataContent();

using StreamContent sc = new StreamContent(CurImg.ToStream());
using StreamContent sc = new StreamContent(CurImg.ToMemStream());

request.Add(sc, "image", Path.GetFileName(CurImg.image_path));

Expand Down Expand Up @@ -2414,8 +2413,8 @@ public static async Task<ClsAIServerResponse> GetDetectionsFromAIServer(ClsImage

//Dictionary<string, byte[]> dict = new Dictionary<string, byte[]>();
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("image", CurImg.ToStream().ConvertToBase64());
string json = JsonConvert.SerializeObject((object)dict);
dict.Add("image", CurImg.ToMemStream().ConvertToBase64());
string json = Global.GetJSONString((object)dict); //JsonConvert.SerializeObject((object)dict);
//byte[] body = Encoding.UTF8.GetBytes(json);
//ByteArrayContent content = new ByteArrayContent(body);
//HttpContent content = Global.CreateHttpContentString(dict);
Expand Down Expand Up @@ -2690,9 +2689,7 @@ public static async Task<ClsAIServerResponse> GetDetectionsFromAIServer(ClsImage

long FileSize = new FileInfo(CurImg.image_path).Length;


cdr.Data = CurImg.ToStream().ConvertToBase64();

cdr.Data = CurImg.ToMemStream().ConvertToBase64();

using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, AiUrl.ToString()))
{
Expand Down Expand Up @@ -2864,7 +2861,7 @@ public static async Task<ClsAIServerResponse> GetDetectionsFromAIServer(ClsImage
// await fileStream.ReadAsync(data, 0, (int)fileStream.Length);
//}

rekognitionImage.Bytes = CurImg.ToStream();
rekognitionImage.Bytes = CurImg.ToMemStream();

dlr.Image = rekognitionImage;

Expand Down Expand Up @@ -2973,7 +2970,7 @@ public static async Task<ClsAIServerResponse> GetDetectionsFromAIServer(ClsImage
// await fileStream.ReadAsync(data, 0, (int)fileStream.Length);
//}

rekognitionImage.Bytes = CurImg.ToStream();
rekognitionImage.Bytes = CurImg.ToMemStream();

dlr.Image = rekognitionImage;
dlr.Attributes.Add("ALL");
Expand Down
4 changes: 2 additions & 2 deletions src/UI/AssemblyInfo1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
// Build Number
// Revision
//
[assembly: AssemblyVersion("2.6.40.8834")]
[assembly: AssemblyFileVersion("2.6.40.8834")]
[assembly: AssemblyVersion("2.6.54.8856")]
[assembly: AssemblyFileVersion("2.6.54.8856")]
92 changes: 69 additions & 23 deletions src/UI/ClsImageQueueItem.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;

namespace AITool
{

public class ClsImageQueueItem
public class ClsImageQueueItem:IDisposable
{
private bool disposedValue;

public string image_path { get; set; } = "";
public DateTime TimeAdded { get; set; } = DateTime.MinValue;
Expand All @@ -29,7 +31,7 @@ public class ClsImageQueueItem
public int Height { get; set; } = 0;
public float DPI { get; set; } = 0;
public long FileSize { get; set; } = 0;
public byte[] ImageByteArray { get; set; } = null;
private byte[] _imageByteArray = null;
private bool _valid { get; set; } = false;
private bool _loaded { get; set; } = false;
private bool _Temp { get; set; } = false;
Expand Down Expand Up @@ -83,18 +85,22 @@ public bool CopyFileTo(string outputFilePath)

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

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

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

using (FileStream fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
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);
}
fileStream.Write(_imageByteArray, 0, _imageByteArray.Length);
}

ret = true;
Expand Down Expand Up @@ -133,18 +139,25 @@ public ClsImageQueueItem(String FileName, long CurQueueSize, bool Temp = false)
}

}
public MemoryStream ToStream()
public Bitmap ToBitmap()
{
using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done.
return new Bitmap(this.ToMemStream());
}

MemoryStream ms = new MemoryStream();
public Image ToImage()
{
return Image.FromStream(this.ToMemStream());
}

public MemoryStream ToMemStream()
{
using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done.

if (this.IsValid())
{
try
{
ms = new MemoryStream(this.ImageByteArray, false);
ms.Flush();
return new MemoryStream(this._imageByteArray);
}
catch (Exception ex)
{
Expand All @@ -155,7 +168,7 @@ public MemoryStream ToStream()
{
AITOOL.Log($"Error: Cannot convert to MemoryStream because image is not valid.");
}
return ms;
return new MemoryStream();
}
public void LoadImage()
{
Expand Down Expand Up @@ -225,14 +238,15 @@ public void LoadImage()
}
else
{
using MemoryStream ms = new MemoryStream();
//using MemoryStream ms = new MemoryStream();
//fileStream.CopyTo(ms);
using MemoryStream ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
this.ImageByteArray = ms.ToArray();
this.FileSize = this.ImageByteArray.Length;
this._imageByteArray = ms.ToArray();
this.FileSize = this._imageByteArray.Length;
this.FileLoadMS = sw.ElapsedMilliseconds;
this._valid = true;
AITOOL.Log($"Trace: 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)}");
AITOOL.Log($"Trace: Image file is valid. Resolution={this.Width}x{this.Height}, LockMS={this.FileLockMS}ms (max={MaxWaitMS}ms), retries={this.FileLockErrRetryCnt}, size={Global.FormatBytes(this._imageByteArray.Length)}: {Path.GetFileName(this.image_path)}");
}
break;
}
Expand Down Expand Up @@ -292,6 +306,38 @@ public void LoadImage()
}
}

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
if (this._imageByteArray != null)
{
this._imageByteArray = null;
AITOOL.Log($"Trace: {Path.GetFileName(this.image_path)} Lifetime was {(DateTime.Now - this.TimeCreated).TotalMilliseconds}");
}
}

// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}

// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~ClsImageQueueItem()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }

void IDisposable.Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
2 changes: 1 addition & 1 deletion src/UI/ClsTelegramMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private ValueTask TelegramBot_OnMakingApiRequest(ITelegramBotClient botClient, T

private ValueTask TelegramBot_OnApiResponseReceived(ITelegramBotClient botClient, Telegram.Bot.Args.ApiResponseEventArgs args, CancellationToken cancellationToken = default)
{
Log($"Trace: API response: {args.ResponseMessage}");
Log($"Trace: API response: {args.ResponseMessage.ToString().CleanString(",")}");
return default;
}

Expand Down
17 changes: 6 additions & 11 deletions src/UI/ClsTriggerActionQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -813,19 +813,13 @@ public async Task<string> MergeImageAnnotations(ClsTriggerActionQueueItem AQI)
AQI.cam.UpdateImageResolutions(AQI.CurImg);

Stopwatch sw = Stopwatch.StartNew();
using (Bitmap img = new Bitmap(AQI.CurImg.ToStream()))

//using MemoryStream ms = AQI.CurImg.ToStream();

using (Bitmap img = AQI.CurImg.ToBitmap())
{
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))
{
List<ClsPrediction> predictions = AQI.Hist.Predictions();
Expand Down Expand Up @@ -1531,7 +1525,8 @@ public async Task<bool> TelegramUpload(ClsTriggerActionQueueItem AQI)
//upload image to Telegram servers and send to first chat
Log($"Debug: uploading image to chat \"{chatid.ReplaceChars('*')}\"", this.CurSrv, AQI.cam, AQI.CurImg);
lastchatid = chatid;
Telegram.Bot.Types.Message message = await AITOOL.Telegram.SendPhotoAsync(chatid, AQI.CurImg.ToStream(), "", AQI.CurImg.image_path, Caption);

Telegram.Bot.Types.Message message = await AITOOL.Telegram.SendPhotoAsync(chatid, AQI.CurImg.ToMemStream(), "", AQI.CurImg.image_path, Caption);

string file_id = message.Photo[0].FileId; //get file_id of uploaded image

Expand Down
4 changes: 2 additions & 2 deletions src/UI/Frm_ObjectDetail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace AITool
{
public partial class Frm_ObjectDetail : Form
public partial class Frm_ObjectDetail:Form
{
public List<ClsPrediction> PredictionObjectDetailsList = null;
public ClsPrediction CurPred = null;
Expand Down Expand Up @@ -67,7 +67,7 @@ private void UpdateImage()
if (!String.IsNullOrEmpty(this.ImageFileName) && this.ImageFileName.Contains("\\") && File.Exists(this.ImageFileName))
{
OriginalBMP = new ClsImageQueueItem(this.ImageFileName, 0);
this.pictureBox1.Image = Image.FromStream(OriginalBMP.ToStream()); //load actual image as background, so that an overlay can be added as the image
this.pictureBox1.Image = OriginalBMP.ToImage(); //load actual image as background, so that an overlay can be added as the image
_lastimage = this.ImageFileName;
}

Expand Down
Loading

0 comments on commit 0eb7d17

Please sign in to comment.