before candybox editor update
This commit is contained in:
@@ -64,14 +64,13 @@ public static class Extensions
|
||||
|
||||
public static IKernel UsePlugins(this IKernel self)
|
||||
{
|
||||
TypeDescriptor.AddAttributes(typeof(List<PluginSettings.PluginFlag>), new TypeConverterAttribute(typeof(PluginFlagConverter)));
|
||||
self.Bind<PluginSettings, ISettings>().ToConstant(new PluginSettings());
|
||||
self.RegisterModule<PluginLoader>();
|
||||
self.Get<PluginLoader>().InitializeModule();
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
public static IKernel InitializeGlobalPlugins(this IKernel self)
|
||||
public static IKernel RegisterGlobalPlugins(this IKernel self)
|
||||
{
|
||||
var plugins = self.Get<PluginLoader>().Plugins;
|
||||
foreach (var plugin in plugins)
|
||||
@@ -89,7 +88,7 @@ public static class Extensions
|
||||
return self;
|
||||
}
|
||||
|
||||
public static IKernel InitializeCameraPlugins(this IChildKernel self, string cameraName)
|
||||
public static IKernel RegisterCameraPlugins(this IChildKernel self, string cameraName)
|
||||
{
|
||||
var plugins = self.Get<PluginLoader>().Plugins;
|
||||
foreach (var plugin in plugins)
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace VisionBuilder.UI.Common.NullClasses;
|
||||
public class NoRecipeCreation: IRecipeCreationTool
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public void CreateRecipe(string recipeName)
|
||||
public bool CreateRecipe(out string recipeName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -9,14 +9,9 @@ namespace VisionBuilder.UI.Common.Plugins;
|
||||
[ModulePriority(1000)]
|
||||
public class PluginLoader:IVisionBuilderModule
|
||||
{
|
||||
private readonly PluginSettings _settings;
|
||||
|
||||
|
||||
|
||||
const string PLUGINS_DIRECTORY = "Plugins";
|
||||
public PluginLoader(PluginSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public List<IPlugin> Plugins { get; } = new List<IPlugin>();
|
||||
|
||||
@@ -31,6 +26,15 @@ public class PluginLoader:IVisionBuilderModule
|
||||
_isInitialized = true;
|
||||
Log.Information("Loading plugins...");
|
||||
var pluginsPath = Path.Combine("..", "Data", PLUGINS_DIRECTORY);
|
||||
|
||||
var enabledPluginsPath = Path.Combine(pluginsPath, "enabled_plugins.txt");
|
||||
if (!File.Exists(enabledPluginsPath))
|
||||
{
|
||||
Log.Warning("Enabled plugins file not found: {EnabledPluginsPath}. All discovered plugins will be disabled by default.", enabledPluginsPath);
|
||||
File.WriteAllText(enabledPluginsPath, "");
|
||||
}
|
||||
|
||||
List<string> enabledPlugins = File.ReadAllLines(enabledPluginsPath).ToList();
|
||||
Directory.CreateDirectory(pluginsPath);
|
||||
var pluginFolders = Directory.GetDirectories(pluginsPath);
|
||||
|
||||
@@ -38,13 +42,8 @@ public class PluginLoader:IVisionBuilderModule
|
||||
{
|
||||
var pluginName = Path.GetFileNameWithoutExtension(folder);
|
||||
|
||||
if (_settings.Plugins.All(p => p.Name != pluginName))
|
||||
{
|
||||
Log.Warning("Discovered new plugin:{PluginName}. Adding to settings.", pluginName);
|
||||
_settings.AddPlugin(new PluginSettings.PluginFlag { Name = pluginName, Enabled = false });
|
||||
}
|
||||
|
||||
if (_settings.Plugins.Where(p => p.Name == pluginName).All(x=>!x.Enabled))
|
||||
if (!enabledPlugins.Contains(pluginName))
|
||||
{
|
||||
Log.Information("Skipping plugin {PluginName} as it is not enabled in settings.", pluginName);
|
||||
continue;
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using Inspectron.Settings;
|
||||
using VisionBuilder.UI.Common.Utils;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Plugins;
|
||||
|
||||
public class PluginSettings:ISettings
|
||||
{
|
||||
private List<PluginFlag> _plugins = new List<PluginFlag>();
|
||||
|
||||
public class PluginFlag
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{(Enabled ? "" : "(Disabled)")}{Name}";
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPlugin(PluginFlag plugin)
|
||||
{
|
||||
if (_plugins.All(p => p.Name != plugin.Name))
|
||||
{
|
||||
_plugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PluginFlag> Plugins
|
||||
{
|
||||
get => _plugins;
|
||||
set
|
||||
{
|
||||
// add unique only
|
||||
foreach (var plugin in value)
|
||||
{
|
||||
AddPlugin(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => this.Plugins,"Plugins",nameof(Plugins));
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginFlagConverter : GeneralListTypeConverter<PluginSettings.PluginFlag>
|
||||
{
|
||||
protected override PluginSettings.PluginFlag ConvertFromString(ITypeDescriptorContext context, CultureInfo culture, string stringValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
return null;
|
||||
var parts = stringValue.Split(',');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
return new PluginSettings.PluginFlag
|
||||
{
|
||||
Name = parts[0].Trim(),
|
||||
Enabled = bool.Parse(parts[1].Trim())
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string ConvertToString(ITypeDescriptorContext context, CultureInfo culture, PluginSettings.PluginFlag errorShortName)
|
||||
{
|
||||
return $"{errorShortName.Name}, {errorShortName.Enabled}";
|
||||
}
|
||||
}
|
||||
191
VisionBuilder.UI.Common/Processing/BaseRecognitionControl.cs
Normal file
191
VisionBuilder.UI.Common/Processing/BaseRecognitionControl.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
|
||||
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)
|
||||
{
|
||||
// Start the recognition process using the Hawkeye library
|
||||
|
||||
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<ImageProcessedEvent> ImageProcessed = delegate { };
|
||||
public event Action<SessionStartedEvent>? SessionStarted = delegate { };
|
||||
public event Action<SessionEndedEvent>? SessionEnded = delegate { };
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Inspectron.Settings;
|
||||
|
||||
namespace VisionBuilder.UI.Common.Processing;
|
||||
|
||||
public abstract class BaseRecognitionControlSettings(string cameraName) : ISettings
|
||||
{
|
||||
public string CameraName { get; } = cameraName;
|
||||
|
||||
public int MinimumProcessingTime { get; set; }
|
||||
public string RecipeNameFilter { get; set; } = "*";
|
||||
|
||||
public virtual void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => this.MinimumProcessingTime, CameraName + "/Hawkeye recognition", "Minimum processing time (ms)");
|
||||
settings.RegisterSimple(this, () => this.RecipeNameFilter, CameraName + "/Hawkeye recognition", nameof(RecipeNameFilter));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace VisionBuilder.UI.Common.RecipeProcessing;
|
||||
namespace VisionBuilder.UI.Common.Processing;
|
||||
|
||||
public interface IImageSource
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace VisionBuilder.UI.Common.RecipeProcessing;
|
||||
namespace VisionBuilder.UI.Common.Processing;
|
||||
|
||||
public interface ILoadingService
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.ViewModel.Classes;
|
||||
|
||||
namespace VisionBuilder.UI.Common.RecipeProcessing;
|
||||
namespace VisionBuilder.UI.Common.Processing;
|
||||
|
||||
public interface IRecognitionControl
|
||||
{
|
||||
|
||||
public bool IsRunning { get; }
|
||||
List<RecipeData> GetRecipesData();
|
||||
void SetRecipe(RecipeData recipe);
|
||||
void Start();
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
public interface IRecipeCreationTool
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public void CreateRecipe(string recipeName);
|
||||
public bool CreateRecipe(out string recipeName);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.Commands.Interfaces;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
using VisionBuilder.UI.Common.Processing;
|
||||
using VisionBuilder.UI.Common.ViewModel;
|
||||
using VisionBuilder.UI.Common.ViewModel.Classes;
|
||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
|
||||
Reference in New Issue
Block a user