diff --git a/SynoAI/AIs/DeepStack/DeepStackAI.cs b/SynoAI/AIs/DeepStack/DeepStackAI.cs index 306b3a6..922c562 100644 --- a/SynoAI/AIs/DeepStack/DeepStackAI.cs +++ b/SynoAI/AIs/DeepStack/DeepStackAI.cs @@ -39,8 +39,7 @@ public async override Task> Process(ILogger logger, Ca DeepStackResponse deepStackResponse = await GetResponse(response); if (deepStackResponse.Success) { - - IEnumerable predictions = deepStackResponse.Predictions.Where(x=> x.Confidence >= minConfidence).Select(x => new AIPrediction() + IEnumerable predictions = deepStackResponse.Predictions.Where(x=> x.Confidence >= minConfidence).Select(x => new AIPrediction() { Confidence = x.Confidence * 100, Label = x.Label, diff --git a/SynoAI/Config.cs b/SynoAI/Config.cs index 654c8c2..8242712 100644 --- a/SynoAI/Config.cs +++ b/SynoAI/Config.cs @@ -99,9 +99,22 @@ public static class Config /// The artificial intelligence system to process the images with. /// public static AIType AI { get; private set; } + /// + /// The URL to access the AI. + /// public static string AIUrl { get; private set; } + /// + /// Development use only. The internal path to call the AI. Potentially a better way to do this would be to support multiple AIs and have separate configs baked into each AI. + /// public static string AIPath { get; private set; } + + /// + /// The default minimum width that an object must be to be considered valid for reporting. Can be overridden on a camera by camera basis to account for different camera resolutions. + /// public static int MinSizeX { get; private set; } + /// + /// The default minimum height that an object must be to be considered valid for reporting. Can be overridden on a camera by camera basis to account for different camera resolutions. + /// public static int MinSizeY { get; private set; } /// diff --git a/SynoAI/Controllers/CameraController.cs b/SynoAI/Controllers/CameraController.cs index 3b5c972..8058367 100644 --- a/SynoAI/Controllers/CameraController.cs +++ b/SynoAI/Controllers/CameraController.cs @@ -61,24 +61,24 @@ public async void Get(string id) // Get the min X and Y values for object; initialize snapshots counter. int minX = camera.GetMinSizeX(); int minY = camera.GetMinSizeY(); - int snapshotCount= 1; + int snapshotCount = 1; // Create the stopwatches for reporting timings Stopwatch overallStopwatch = Stopwatch.StartNew(); - //Start bucle for asking snapshots until a valid prediction is found or MaxSnapshots is reached - while (snapshotCount > 0 && snapshotCount <= Config.MaxSnapshots) + // Start loop for requesting snapshots until a valid prediction is found or MaxSnapshots is reached + while (snapshotCount <= Config.MaxSnapshots) { - _logger.LogInformation($"Snapshot {snapshotCount} of {Config.MaxSnapshots} asked at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms."); // Take the snapshot from Surveillance Station + _logger.LogInformation($"Snapshot {snapshotCount} of {Config.MaxSnapshots} asked at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms."); byte[] snapshot = await GetSnapshot(id); _logger.LogInformation($"Snapshot {snapshotCount} of {Config.MaxSnapshots} received at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms."); - //See if the image needs to be rotated (or further processing in the future ?) previous to being analyzed by AI + // See if the image needs to be rotated (or further processing in the future ?) before being analyzed by the AI snapshot = PreProcessSnapshot(camera, snapshot); - // Use the AI to get the valid predictions and then get all the valid predictions, which are all the AI predictions where the result from the AI is - // in the list of types and where the size of the object is bigger than the defined value. + // Use the AI to get the valid predictions and then get all the valid predictions (which are all the AI predictions where the result from the AI is + // in the list of types and where the size of the object is bigger than the defined value). IEnumerable predictions = await GetAIPredications(camera, snapshot); _logger.LogInformation($"Snapshot {snapshotCount} of {Config.MaxSnapshots} processed {predictions.Count()} objects at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms."); @@ -92,7 +92,6 @@ public async void Get(string id) if (validPredictions.Count() > 0) { - // Because we don't want to process the image if it isn't even required, then we pass the snapshot manager to the notifiers. It will then perform // the necessary actions when it's GetImage method is called. SnapshotManager snapshotManager = new SnapshotManager(snapshot, predictions, validPredictions, _snapshotManagerLogger); @@ -105,12 +104,13 @@ public async void Get(string id) } // Generate text for notifications - IList labels = new List(); - + List labels = new List(); if (Config.AlternativeLabelling && Config.DrawMode == DrawMode.Matches) { if (validPredictions.Count() == 1) { + // If there is only a single object, then don't add a correlating number and instead just + // write out the label. decimal confidence = Math.Round(validPredictions.First().Confidence, 0, MidpointRounding.AwayFromZero); labels.Add($"{validPredictions.First().Label.FirstCharToUpper()} {confidence}%"); } @@ -131,12 +131,10 @@ public async void Get(string id) labels = validPredictions.Select(x => x.Label.FirstCharToUpper()).ToList(); } - //Send Notifications + // Send Notifications await SendNotifications(camera, snapshotManager, labels); _logger.LogInformation($"{id}: Valid object found in snapshot {snapshotCount} of {Config.MaxSnapshots} at EVENT TIME {overallStopwatch.ElapsedMilliseconds}ms."); - - //Stop snapshot bucle iteration: - snapshotCount = -1; + break; } else if (predictions.Count() > 0) { diff --git a/SynoAI/Notifiers/Email/Email.cs b/SynoAI/Notifiers/Email/Email.cs index 906f14b..174a0fd 100644 --- a/SynoAI/Notifiers/Email/Email.cs +++ b/SynoAI/Notifiers/Email/Email.cs @@ -57,7 +57,7 @@ public class Email : NotifierBase /// A thread safe object for fetching the processed image. /// The list of types that were found. /// A logger. - public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IList foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IEnumerable foundTypes, ILogger logger) { using (logger.BeginScope($"Email '{Destination}'")) { diff --git a/SynoAI/Notifiers/INotifier.cs b/SynoAI/Notifiers/INotifier.cs index 7161fce..176ba08 100644 --- a/SynoAI/Notifiers/INotifier.cs +++ b/SynoAI/Notifiers/INotifier.cs @@ -15,6 +15,6 @@ public interface INotifier /// /// Handles the send of the notification. /// - Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IList foundTypes, ILogger logger); + Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IEnumerable foundTypes, ILogger logger); } } diff --git a/SynoAI/Notifiers/NotifierBase.cs b/SynoAI/Notifiers/NotifierBase.cs index e3122f5..3c4306f 100644 --- a/SynoAI/Notifiers/NotifierBase.cs +++ b/SynoAI/Notifiers/NotifierBase.cs @@ -12,9 +12,9 @@ namespace SynoAI.Notifiers public abstract class NotifierBase : INotifier { public IEnumerable Cameras { get; set;} - public abstract Task SendAsync(Camera camera, ISnapshotManager fileAccessor, IList foundTypes, ILogger logger); + public abstract Task SendAsync(Camera camera, ISnapshotManager fileAccessor, IEnumerable foundTypes, ILogger logger); - protected string GetMessage(Camera camera, IList foundTypes) + protected string GetMessage(Camera camera, IEnumerable foundTypes) { if (Config.AlternativeLabelling && Config.DrawMode == DrawMode.Matches) { diff --git a/SynoAI/Notifiers/Pushbullet/Pushbullet.cs b/SynoAI/Notifiers/Pushbullet/Pushbullet.cs index 4384241..c1d0eea 100644 --- a/SynoAI/Notifiers/Pushbullet/Pushbullet.cs +++ b/SynoAI/Notifiers/Pushbullet/Pushbullet.cs @@ -34,7 +34,7 @@ public class Pushbullet : NotifierBase /// A thread safe object for fetching the processed image. /// The list of types that were found. /// A logger. - public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IList foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IEnumerable foundTypes, ILogger logger) { // Pushbullet file uploads are a two part process. First we need to request to upload a file using (HttpClient client = new HttpClient()) diff --git a/SynoAI/Notifiers/Telegram/Telegram.cs b/SynoAI/Notifiers/Telegram/Telegram.cs index a714345..c4147b8 100644 --- a/SynoAI/Notifiers/Telegram/Telegram.cs +++ b/SynoAI/Notifiers/Telegram/Telegram.cs @@ -42,7 +42,7 @@ public class Telegram : NotifierBase /// A thread safe object for fetching the processed image. /// The list of types that were found. /// A logger. - public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IList foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IEnumerable foundTypes, ILogger logger) { using (logger.BeginScope($"Telegram '{ChatID}'")) { diff --git a/SynoAI/Notifiers/Webhook/Webhook.cs b/SynoAI/Notifiers/Webhook/Webhook.cs index 2dd793f..fc77eb7 100644 --- a/SynoAI/Notifiers/Webhook/Webhook.cs +++ b/SynoAI/Notifiers/Webhook/Webhook.cs @@ -65,7 +65,7 @@ public class Webhook : NotifierBase /// A thread safe object for fetching a readonly file stream. /// The list of types that were found. /// A logger. - public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IList foundTypes, ILogger logger) + public override async Task SendAsync(Camera camera, ISnapshotManager snapshotManager, IEnumerable foundTypes, ILogger logger) { logger.LogInformation($"{camera.Name}: Webhook: Processing"); using (HttpClient client = new HttpClient()) diff --git a/SynoAI/Services/SnapshotManager.cs b/SynoAI/Services/SnapshotManager.cs index 3053cb5..9da133c 100644 --- a/SynoAI/Services/SnapshotManager.cs +++ b/SynoAI/Services/SnapshotManager.cs @@ -112,12 +112,11 @@ private SKBitmap ProcessImage(Camera camera) StrokeWidth = Config.StrokeWidth }); - //Label creation, either classic label or alternative labelling (and only if there is more than one object) + // Label creation, either classic label or alternative labelling (and only if there is more than one object) string label = String.Empty; - if (Config.AlternativeLabelling && Config.DrawMode == DrawMode.Matches) { - //On alternatie labelling, just place a reference number and only if there is more than one object + // On alternatie labelling, just place a reference number and only if there is more than one object if (_validPredictions.Count() > 1) { label = counter.ToString(); @@ -130,11 +129,11 @@ private SKBitmap ProcessImage(Camera camera) label = $"{prediction.Label.FirstCharToUpper()} {confidence}%"; } - //Label positioning + // Label positioning int x = prediction.MinX + Config.TextOffsetX; int y = prediction.MinY + Config.FontSize + Config.TextOffsetY; - //Consider below box placement + // Consider below box placement if (Config.LabelBelowBox) { y += prediction.SizeY; @@ -190,7 +189,7 @@ private static string SaveImage(ILogger logger, Camera camera, SKBitmap image, s { fileName += "_" + suffix; } - fileName+= ".jpeg"; + fileName += ".jpeg"; string filePath = Path.Combine(directory, fileName); logger.LogInformation($"{camera}: Saving image to '{filePath}'.");