using System.Diagnostics; using OpenCvSharp; using Serilog; using VisionBuilder.UI.Common.Commands; using VisionBuilder.UI.Common.ViewModel.Classes; namespace VisionBuilder.UI.Common.Processing; public abstract class BaseRecognitionControl: IRecognitionControl { private readonly BaseRecognitionControlSettings _settings; private readonly ILoadingService _loadingService; private CancellationTokenSource _cts; private Task _loopTask; public BaseRecognitionControl(BaseRecognitionControlSettings settings, ILoadingService loadingService) { _settings = settings; _loadingService = loadingService; } public abstract List GetRecipesData(); RecipeData _currentRecipe; public void SetRecipe(RecipeData recipe) { // error if running if (IsRunning) { throw new InvalidOperationException("Cannot change recipe while recognition is running."); } _currentRecipe = recipe; } public bool IsRunning { get; set; } public bool IsPaused { get; set; } public RecipeData CurrentRecipe => _currentRecipe; public ILoadingService LoadingService => _loadingService; private DateTime _startTime; public void Start() { if (_currentRecipe == null) { throw new InvalidOperationException("No recipe set."); } if (IsRunning) { throw new InvalidOperationException("Recognition is already running."); } _loadingService.StartLoading($"Starting recipe {_currentRecipe.RecipeName} on {_settings.CameraName}..."); Initialize(_currentRecipe); _startTime = DateTime.Now; IsRunning = true; _cts = new CancellationTokenSource(); Log.Information($"Warming up"); try { var swWarmup = Stopwatch.StartNew(); WarmUp(); swWarmup.Stop(); Log.Information($"Warmup run took {swWarmup.ElapsedMilliseconds} ms"); } catch (Exception ex) { Log.Error(ex, "Error during warmup run"); } _loopTask = Task.Run(() => Loop(_cts.Token), _cts.Token); SessionStarted?.Invoke(new SessionStartedEvent { RecipeName = _currentRecipe.RecipeName, SessionStarted = _startTime, }); _loadingService.StopLoading($"Starting recipe {_currentRecipe.RecipeName} on {_settings.CameraName}..."); } protected abstract void Initialize(RecipeData currentRecipe); protected abstract void WarmUp(); protected abstract (Mat originalImage, Mat analysisImage, TimeSpan processingTime, TimeSpan acquisitionTime, string[] errorNames)? ProcessImage(CancellationToken token); public void Stop() { if (!IsRunning) return; _loadingService.StartLoading($"Stopping {_settings.CameraName}"); _cts?.Cancel(); try { _loopTask?.Wait(); } catch (TaskCanceledException) { // Ignore cancellation exceptions } catch (AggregateException ex) when (ex.Flatten().InnerExceptions.Any(x=>x is TaskCanceledException)) { // Ignore cancellation exceptions } catch(Exception ex) { Log.Error(ex, "Error while stopping recognition loop"); } finally { IsRunning = false; _cts?.Dispose(); _cts = null; _loopTask = null; } SessionEnded?.Invoke(new SessionEndedEvent { SessionEnded = DateTime.Now }); _loadingService.StopLoading($"Stopping {_settings.CameraName}"); } public void Pause() { IsPaused = true; } public void Resume() { IsPaused = false; } private async Task Loop(CancellationToken token) { while (!token.IsCancellationRequested) { if (IsPaused) { await Task.Delay(100, token); continue; } var processed = ProcessImage(token); if (processed == null) { return; } if (processed.Value.processingTime.Milliseconds < _settings.MinimumProcessingTime) { Thread.Sleep(_settings.MinimumProcessingTime - (int)processed.Value.processingTime.Milliseconds); } var processingResult = new ImageProcessedEvent() { SessionStart = _startTime, RecipeName = _currentRecipe.RecipeName, AnalysisTime = processed.Value.processingTime - processed.Value.acquisitionTime, ErrorNames = processed.Value.errorNames.ToList(), HasError = processed.Value.errorNames.Length > 0, ImageSource = _settings.CameraName, ImageOriginal = processed.Value.originalImage, ImageAnalysis = processed.Value.analysisImage }; var swProcessed = Stopwatch.StartNew(); ImageProcessed(processingResult); swProcessed.Stop(); Log.Information($"ImageProcessed event handled in {swProcessed.ElapsedMilliseconds} ms"); } } public event Action ImageProcessed = delegate { }; public event Action? SessionStarted = delegate { }; public event Action? SessionEnded = delegate { }; }