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) { lock (_lock) { // 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; object _lock = new object(); public void Start() { lock (_lock) { 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() { lock (_lock) { 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; Cleanup(); } SessionEnded?.Invoke(new SessionEndedEvent { SessionEnded = DateTime.Now }); _loadingService.StopLoading($"Stopping {_settings.CameraName}"); // note: recipe can be stared paused //IsPaused = false; // ensure paused is reset } } public void Pause() { lock (_lock) { Log.Information("Pausing recognition"); IsPaused = true; } } public void Resume() { lock (_lock) { Log.Information("Resuming recognition"); IsPaused = false; } } int _errorsInSequence = 0; private async Task Loop(CancellationToken token) { while (!token.IsCancellationRequested) { if (IsPaused) { await Task.Delay(100, token).ConfigureAwait(false); continue; } var processed = ProcessImage(token); if (processed == null) { return; } int sleepTime=0; if (processed.Value.processingTime.Milliseconds < _settings.MinimumProcessingTime) { sleepTime = _settings.MinimumProcessingTime - (int)processed.Value.processingTime.Milliseconds; Log.Debug("Sleeping for {sleepTime}",sleepTime); Thread.Sleep(sleepTime); } if (processed.Value.errorNames.Length > 0) { _errorsInSequence++; if (_errorsInSequence >= _settings.ErrorsInSequenceAlarm) { ErrorsInSequenceAlarm(new ErrorsInSequenceEvent(){Errors = _errorsInSequence}); } } else { _errorsInSequence = 0; } var processingResult = new ImageProcessedEvent() { SessionStart = _startTime, RecipeName = _currentRecipe.RecipeName, AnalysisTime = processed.Value.processingTime - processed.Value.acquisitionTime, AcquisitionTime = processed.Value.acquisitionTime, SleepTime = TimeSpan.FromMilliseconds(sleepTime), 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 {processed} ms", swProcessed.ElapsedMilliseconds); } } public event Action ImageProcessed = delegate { }; public event Action SessionStarted = delegate { }; public event Action SessionEnded = delegate { }; public event Action ErrorsInSequenceAlarm = delegate { }; public event Action RecipeFileChanged = delegate { }; public virtual void HotReload() { } protected void RaiseRecipeFileChanged() => RecipeFileChanged?.Invoke(); protected virtual void Cleanup() { } }