241 lines
6.9 KiB
C#
241 lines
6.9 KiB
C#
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<RecipeData> 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;
|
|
}
|
|
|
|
SessionEnded?.Invoke(new SessionEndedEvent
|
|
{
|
|
SessionEnded = DateTime.Now
|
|
});
|
|
_loadingService.StopLoading($"Stopping {_settings.CameraName}");
|
|
IsPaused = false; // ensure paused is reset
|
|
}
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
IsPaused = true;
|
|
}
|
|
}
|
|
|
|
public void Resume()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
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<ImageProcessedEvent> ImageProcessed = delegate { };
|
|
public event Action<SessionStartedEvent> SessionStarted = delegate { };
|
|
public event Action<SessionEndedEvent> SessionEnded = delegate { };
|
|
public event Action<ErrorsInSequenceEvent> ErrorsInSequenceAlarm = delegate { };
|
|
} |