diff --git a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeCameraImageSource.cs b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeCameraImageSource.cs index 7f47d32..2d6082a 100644 --- a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeCameraImageSource.cs +++ b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeCameraImageSource.cs @@ -3,6 +3,7 @@ using OpenCvSharp; using Serilog; using System.Net; using System.Runtime.InteropServices; +using Newtonsoft.Json; using VisionBuilder.UI.Common; using VisionBuilder.UI.Common.RecipeProcessing; using CameraSettings = Inspectron.HawkEye.Protocol.CameraSettings; @@ -28,31 +29,54 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye IPAddress.Parse(_settings.Adapter)); _client.ImageReceived += _client_ImageReceived; _client.SettingsReceived += _client_SettingsReceived; + _client.CalibrationReceived += _client_CalibrationReceived; + _client.SupportsCalibration = _settings.IsColor; _client.Connect(); Log.Information("Connected to Hawkeye camera at {Adapter}", _settings.Adapter, 27001); } + + private void _client_CalibrationReceived(List> obj) + { + Log.Information("Calibtating..."); + _bayerFilter = new OpenCVBayerProcessor(); + _bayerFilter.SetCalibration(obj); + } + private void _client_SettingsReceived(CameraSettings obj) { ApplySettings(obj); } public void ApplySettings(CameraSettings obj) { - _cameraSettings = UICameraSettings.LoadSettingsLocal(_settings.SettingsFile).ToCameraSettings(); - _client.ApplySettings(_cameraSettings); + _cameraSettings= obj; + if (File.Exists(_settings?.SettingsFile)) + { + _cameraSettings = UICameraSettings.LoadSettingsLocal(_settings.SettingsFile).ToCameraSettings(); + _client.ApplySettings(_cameraSettings); + } + + } public Task GetImage(CancellationToken token) { _lastImage = null; - if (_imageAquisitionTaskSource != null && !_imageAquisitionTaskSource.Task.IsCanceled) + if (_imageAquisitionTaskSource != null && !_imageAquisitionTaskSource.Task.IsCanceled && !_imageAquisitionTaskSource.Task.IsCompleted) { _imageAquisitionTaskSource.SetCanceled(CancellationToken.None); _imageAquisitionTaskSource = null; } _imageAquisitionTaskSource= new TaskCompletionSource(); + if (token.CanBeCanceled) + { + token.Register(() => + { + _imageAquisitionTaskSource.TrySetCanceled(token); + }); + } _client.Trigger(); return _imageAquisitionTaskSource.Task; @@ -61,31 +85,52 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye private TaskCompletionSource? _imageAquisitionTaskSource; private Mat _lastImage; byte[] _flipBuffer = new byte[2000 * 2000]; + private OpenCVBayerProcessor _bayerFilter; + private void _client_ImageReceived(byte[] obj) { - Log.Debug("Got image on {adapter}", _settings.Adapter); - FlipLines(obj, _flipBuffer); - obj = _flipBuffer; - var pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned); - var pointer = pinnedArray.AddrOfPinnedObject(); - - Mat image = new Mat(_cameraSettings.ImageSettings.Lines, _cameraSettings.ImageSettings.SensorWidth, - MatType.CV_8UC1, pointer); - - var xCrop = _cameraSettings.ImageSettings.SensorWidth - _cameraSettings.ImageWidth - _cameraSettings.OffsetX; - - // crop the image with opencv - _lastImage = image[0, _cameraSettings.ImageSettings.Lines, xCrop, - xCrop + _cameraSettings.ImageWidth].Clone(); - - if (_cameraSettings.BayerFilter) + try { - _lastImage = BayerFilter(_lastImage); + Log.Debug("Got image on {adapter}", _settings.Adapter); + //FlipLines(obj, _flipBuffer); + + var pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned); + var pointer = pinnedArray.AddrOfPinnedObject(); + + + Mat image = new Mat(_cameraSettings.ImageSettings.Lines, _cameraSettings.ImageSettings.SensorWidth, + MatType.CV_8UC1, pointer); + + + + var xCrop = _cameraSettings.ImageSettings.SensorWidth - _cameraSettings.ImageWidth - + _cameraSettings.OffsetX; + + // crop the image with opencv + _lastImage = image[0, _cameraSettings.ImageSettings.Lines, xCrop, + xCrop + _cameraSettings.ImageWidth].Clone(); + + + + + if (_cameraSettings.BayerFilter) + { + _lastImage = _bayerFilter.ProcessBayerImageWithChannelControl(_lastImage); + } + + // rotate 90 degrees CCW + Cv2.Rotate(_lastImage, _lastImage, RotateFlags.Rotate90Counterclockwise); + + //_lastImage = _lastImage.CvtColor(ColorConversionCodes.BGR2RGB); + + _imageAquisitionTaskSource!.SetResult(_lastImage); + + pinnedArray.Free(); + } + catch (Exception ex) + { + Log.Error(ex, "Error processing image from Hawkeye camera at {Adapter}", _settings.Adapter); } - - _imageAquisitionTaskSource!.SetResult(_lastImage); - - pinnedArray.Free(); } Mat BayerFilter(Mat image) @@ -96,8 +141,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye throw new ArgumentException("Input image is null or empty.", nameof(image)); Mat bgrImage = new Mat(); - // Use OpenCV's demosaicing function for Bayer BG pattern - Cv2.CvtColor(image, bgrImage, ColorConversionCodes.BayerBG2BGR); + Cv2.CvtColor(image, bgrImage, ColorConversionCodes.BayerRG2BGR); return bgrImage; } @@ -127,7 +171,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye public void InitializeModule() { - + Open(); } } } diff --git a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeSettings.cs b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeSettings.cs index 7989583..dae5b4a 100644 --- a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeSettings.cs +++ b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/HawkeyeSettings.cs @@ -14,10 +14,13 @@ public class HawkeyeSettings(string CameraName): ISettings [File("*.jcnf")] public string SettingsFile { get; set; } + public bool IsColor { get; set; }=true; public void RegisterSettings(InspectronSettings settings) { settings.RegisterSimple(this, () => Adapter, $"{CameraName}/Sources/Hawkeye", nameof(Adapter)); + settings.RegisterSimple(this, () => IsColor, $"{CameraName}/Sources/Hawkeye", nameof(IsColor)); + settings.RegisterSimple(this, () => SettingsFile, $"{CameraName}/Sources/Hawkeye", nameof(SettingsFile)); } diff --git a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs new file mode 100644 index 0000000..c4453b3 --- /dev/null +++ b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs @@ -0,0 +1,124 @@ +using NLog.Filters; +using OpenCvSharp; + +namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye; + +public class OpenCVBayerProcessor +{ + private SplineInterpolator GreenInterpolation { get; set; } + private SplineInterpolator RedInterpolation { get; set; } + private SplineInterpolator BlueInterpolation { get; set; } + + // Cached lookup tables + private byte[] redLut; + private byte[] greenLut; + private byte[] blueLut; + private Mat _lookupTable; + + public OpenCVBayerProcessor() + { + // Initialize with default linear interpolation (same as your defaults) + GreenInterpolation = new SplineInterpolator(new Dictionary() + {{0,0}, { 100, 100 }, { 255,255}}); + RedInterpolation = new SplineInterpolator(new Dictionary() + {{0,0}, { 100, 100 }, { 255,255}}); + BlueInterpolation = new SplineInterpolator(new Dictionary() + {{0,0}, { 100, 100 }, { 255,255}}); + + UpdateLookupTables(); + } + + public void SetInterpolators(SplineInterpolator red, SplineInterpolator green, SplineInterpolator blue) + { + RedInterpolation = red; + GreenInterpolation = green; + BlueInterpolation = blue; + UpdateLookupTables(); + } + + public void SetCalibration(List> calibration) + { + RedInterpolation = new SplineInterpolator(calibration[0]); + GreenInterpolation = new SplineInterpolator(calibration[1]); + BlueInterpolation = new SplineInterpolator(calibration[2]); + UpdateLookupTables(); + } + + private void UpdateLookupTables() + { + redLut = Enumerable.Range(0, 256).Select(x => (byte)RedInterpolation.GetValue(x)).ToArray(); + greenLut = Enumerable.Range(0, 256).Select(x => (byte)GreenInterpolation.GetValue(x)).ToArray(); + blueLut = Enumerable.Range(0, 256).Select(x => (byte)BlueInterpolation.GetValue(x)).ToArray(); + + // Create lookup table for all three channels + _lookupTable = new Mat(1, 256, MatType.CV_8UC3); + + // Use the Mat indexer for safe access + var indexer = _lookupTable.GetGenericIndexer(); + for (int i = 0; i < 256; i++) + { + indexer[0, i] = new Vec3b(blueLut[i], greenLut[i], redLut[i]); + } + } + + public Mat ProcessBayerImage(Mat grayImage) + { + // Step 1: Apply Bayer demosaicing with RG pattern (matching your pattern) + Mat colorImage = new Mat(); + Cv2.CvtColor(grayImage, colorImage, ColorConversionCodes.BayerBG2BGR); + + // Step 2: Apply color interpolation/correction using lookup tables + Mat correctedImage = ApplyColorCorrection(colorImage); + + return correctedImage; + } + + private Mat ApplyColorCorrection(Mat colorImage) + { + + + // Apply the lookup table + Mat result = new Mat(); + Cv2.LUT(colorImage, _lookupTable, result); + + + return result; + } + + // Alternative: Apply correction per channel if you need more control + public Mat ProcessBayerImageWithChannelControl(Mat grayImage) + { + // Step 1: Demosaic + Mat colorImage = new Mat(); + Cv2.CvtColor(grayImage, colorImage, ColorConversionCodes.BayerBG2BGR); + + // Step 2: Split channels + Mat[] channels = Cv2.Split(colorImage); + + // Step 3: Apply individual LUTs to each channel + Mat blueCorrected = new Mat(); + Mat greenCorrected = new Mat(); + Mat redCorrected = new Mat(); + + Mat blueLutMat = new Mat(1, 256, MatType.CV_8U, blueLut); + Mat greenLutMat = new Mat(1, 256, MatType.CV_8U, greenLut); + Mat redLutMat = new Mat(1, 256, MatType.CV_8U, redLut); + + Cv2.LUT(channels[0], blueLutMat, blueCorrected); + Cv2.LUT(channels[1], greenLutMat, greenCorrected); + Cv2.LUT(channels[2], redLutMat, redCorrected); + + // Step 4: Merge channels back + Mat result = new Mat(); + Cv2.Merge(new Mat[] { blueCorrected, greenCorrected, redCorrected }, result); + + // Cleanup + foreach (var channel in channels) channel.Dispose(); + blueCorrected.Dispose(); + greenCorrected.Dispose(); + redCorrected.Dispose(); + colorImage.Dispose(); + + return result; + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs new file mode 100644 index 0000000..01a96f5 --- /dev/null +++ b/Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs @@ -0,0 +1,138 @@ +namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye; + +public class SplineInterpolator +{ + private readonly Dictionary _nodes; + private readonly double[] _keys; + + private readonly double[] _values; + + private readonly double[] _h; + + private readonly double[] _a; + + /// + /// Class constructor. + /// + /// Collection of known points for further interpolation. + /// Should contain at least two items. + public SplineInterpolator(Dictionary nodes) + { + if (nodes == null) + { + throw new ArgumentNullException("nodes"); + } + + _nodes = nodes; + + var n = nodes.Count; + + if (n < 2) + { + throw new ArgumentException("At least two point required for interpolation."); + } + + _keys = nodes.Keys.ToArray(); + _values = nodes.Values.ToArray(); + _a = new double[n]; + _h = new double[n]; + + for (int i = 1; i < n; i++) + { + _h[i] = _keys[i] - _keys[i - 1]; + } + + if (n > 2) + { + var sub = new double[n - 1]; + var diag = new double[n - 1]; + var sup = new double[n - 1]; + + for (int i = 1; i <= n - 2; i++) + { + diag[i] = (_h[i] + _h[i + 1]) / 3; + sup[i] = _h[i + 1] / 6; + sub[i] = _h[i] / 6; + _a[i] = (_values[i + 1] - _values[i]) / _h[i + 1] - (_values[i] - _values[i - 1]) / _h[i]; + } + + SolveTridiag(sub, diag, sup, ref _a, n - 2); + } + } + + public double[] Keys => _keys; + + public double[] Values => _values; + + public Dictionary Nodes => _nodes; + + /// + /// Gets interpolated value for specified argument. + /// + /// Argument value for interpolation. Must be within + /// the interval bounded by lowest ang highest values. + public double GetValue(double key) + { + int gap = 0; + var previous = double.MinValue; + + + if (key > _keys.Max()) key = _keys.Max(); + if (key < _keys.Min()) key = _keys.Min(); + + for (int i = 0; i < _keys.Length; i++) + { + if (Math.Abs(_keys[i] - key) < 0.001) + { + return _values[i]; + } + } + + + + // At the end of this iteration, "gap" will contain the index of the interval + // between two known values, which contains the unknown z, and "previous" will + // contain the biggest z value among the known samples, left of the unknown z + for (int i = 0; i < _keys.Length; i++) + { + if (_keys[i] < key && _keys[i] > previous) + { + previous = _keys[i]; + gap = i + 1; + } + } + + var x1 = key - previous; + var x2 = _h[gap] - x1; + + var res = ((-_a[gap - 1] / 6 * (x2 + _h[gap]) * x1 + _values[gap - 1]) * x2 + + (-_a[gap] / 6 * (x1 + _h[gap]) * x2 + _values[gap]) * x1) / _h[gap]; + if (res > 255) res = 255; + if (res < 0) res = 0; + return res; + } + + + /// + /// Solve linear system with tridiagonal n*n matrix "a" + /// using Gaussian elimination without pivoting. + /// + private static void SolveTridiag(double[] sub, double[] diag, double[] sup, ref double[] b, int n) + { + int i; + + for (i = 2; i <= n; i++) + { + sub[i] = sub[i] / diag[i - 1]; + diag[i] = diag[i] - sub[i] * sup[i - 1]; + b[i] = b[i] - sub[i] * b[i - 1]; + } + + b[n] = b[n] / diag[n]; + + for (i = n - 1; i >= 1; i--) + { + b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i]; + } + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/AI/OnnxModelOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/AI/OnnxModelOperation.cs new file mode 100644 index 0000000..720a45b --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/Operations/AI/OnnxModelOperation.cs @@ -0,0 +1,136 @@ +using Hawkeye.VisionBuilder.Workflow.Datatypes; +using Hawkeye.VisionBuilder.Workflow.Operations.Attributes; +using ILGPU.Runtime.Cuda; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; +using Microsoft.Scripting.Runtime; +using OpenCvSharp; +namespace Hawkeye.VisionBuilder.Workflow.Operations.AI; + +[Category("AI")] +public class OnnxModelOperation:BaseOperation +{ + + private FilePath _modelFilePath = new FilePath(){Format = "ONNX file(*.onnx)|*.onnx" }; + + + public FilePath ModelFilePath + { + get => _modelFilePath; + set + { + _modelFilePath = value; + _initialized = false; + } + } + + private bool _initialized = false; + private InferenceSession _AIsession; + private string _inputName; + private int[] _dimensions; + + protected override void InterpretInternal(Context context) + { + if (!CheckImageExists(context)) return; + if (!CheckColorful(context)) return; + + if (!_initialized) + { + if (!File.Exists(ModelFilePath?.Path)) + { + this.SetError($"Model file \"{ModelFilePath?.Path}\" not found"); + return; + } + + var opts = new SessionOptions(); + //opts.AppendExecutionProvider_DML(0); + _AIsession = new InferenceSession(ModelFilePath?.Path, opts); + _inputName = _AIsession.InputMetadata.First().Key; + _dimensions = _AIsession.InputMetadata.First().Value.Dimensions; + foreach (var input in _AIsession.InputMetadata) + { + Console.WriteLine($"Name: {input.Key}"); + Console.WriteLine($" Type: {input.Value.ElementType}"); + Console.WriteLine($" Shape: [{string.Join(", ", input.Value.Dimensions)}]"); + } + + _initialized = true; + } + + var currentImage = context.ActiveImage; + + var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB); + + // convert to float32 + var floatImage = new Mat(); + rightColor.ConvertTo(floatImage, MatType.CV_32FC3, 1.0 / 255); + // to array + + var width = _dimensions[1]; + var height = _dimensions[2]; + + // check if need resize + if (floatImage.Width != width || floatImage.Height != height) + Cv2.Resize(floatImage, floatImage, new OpenCvSharp.Size(width, height)); + + + float[] data = new float[3 * width * height]; + int idx = 0; + var rows = floatImage.Rows; + var cols = floatImage.Cols; + for (int y = 0; y < rows; y++) + { + for (int x = 0; x < cols; x++) + { + Vec3f pixel = floatImage.At(y, x); + for (int c = 0; c < 3; c++) // channels + { + data[idx++] = pixel[c]; // width*height*channel + } + } + } + + var inputTensor = new DenseTensor( + data, + new int[] { 1, floatImage.Width, floatImage.Height, 3 } + ); + + var inputs = new List { + NamedOnnxValue.CreateFromTensor("input", inputTensor) + }; + var sw = System.Diagnostics.Stopwatch.StartNew(); + using var results = _AIsession.Run(inputs); + sw.Stop(); + Console.WriteLine($"Inference time: {sw.ElapsedMilliseconds} ms"); + var output = results.First().AsEnumerable().ToArray(); + + // convert output to Mat and normalize to 0-255 + var outputMat = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_32FC1); + outputMat.SetArray(output); + + outputMat.ConvertTo(outputMat, MatType.CV_8UC1, 255); + + // resize back to original size + Cv2.Resize(outputMat, outputMat, new OpenCvSharp.Size(currentImage.ImageData.Width, currentImage.ImageData.Height)); + + + context.ActiveImage = new HawkeyeImage() + { + ImageData = outputMat + }; + + } + + public override void Save(Dictionary dict) + { + base.Save(dict); + dict[nameof(ModelFilePath)] = ModelFilePath?.Path; + } + + public override void Load(Dictionary dict) + { + base.Load(dict); + if (dict.ContainsKey(nameof(ModelFilePath))) + ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString(); + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder/Hawkeye.VisionBuilder.csproj b/Hawkeye.VisionBuilder/Hawkeye.VisionBuilder.csproj index 8cc3630..9891cd5 100644 --- a/Hawkeye.VisionBuilder/Hawkeye.VisionBuilder.csproj +++ b/Hawkeye.VisionBuilder/Hawkeye.VisionBuilder.csproj @@ -16,6 +16,7 @@ + diff --git a/Hawkeye.VisionBuilder/MainWindow.cs b/Hawkeye.VisionBuilder/MainWindow.cs index 73765a7..3b7c6e9 100644 --- a/Hawkeye.VisionBuilder/MainWindow.cs +++ b/Hawkeye.VisionBuilder/MainWindow.cs @@ -264,6 +264,7 @@ namespace Hawkeye.VisionBuilder var ext = Path.GetExtension(dialog.FileName); if (ext == ".jhrcp") { + _workflowList.RecipeImage = _workflowList.Context.LastCameraImage?.ImageData; _workflowList.SaveJSON(dialog.FileName); } else diff --git a/Plugins/CandyboxPlugin/CandyboxPlugin.csproj b/Plugins/CandyboxPlugin/CandyboxPlugin.csproj new file mode 100644 index 0000000..ba7a405 --- /dev/null +++ b/Plugins/CandyboxPlugin/CandyboxPlugin.csproj @@ -0,0 +1,28 @@ + + + + net8.0-windows + true + enable + true + enable + ..\..\VisionBuilder.UI.Windows.Test\bin\Debug\Data\Plugins\CandyboxPlugin + + + + + + False + runtime + + + False + runtime + + + false + runtime + + + + diff --git a/Plugins/CandyboxPlugin/Class1.cs b/Plugins/CandyboxPlugin/Class1.cs new file mode 100644 index 0000000..f40cb66 --- /dev/null +++ b/Plugins/CandyboxPlugin/Class1.cs @@ -0,0 +1,7 @@ +namespace CandyboxPlugin +{ + public class Class1 + { + + } +} diff --git a/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReader.csproj b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReader.csproj new file mode 100644 index 0000000..97a2d1f --- /dev/null +++ b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReader.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + true + ..\..\..\VisionBuilder.UI.Windows.Test\bin\Debug\Data\Plugins\PackstrasseBarcodeReader + + + + + + + + + + False + runtime + + + False + runtime + + + + diff --git a/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderModule.cs b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderModule.cs new file mode 100644 index 0000000..6b7a6d9 --- /dev/null +++ b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderModule.cs @@ -0,0 +1,63 @@ +using System.IO.Ports; +using System.Security.AccessControl; +using System.Text; +using Serilog; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.RecipeProcessing; + +namespace PackstrasseBarcodeReader +{ + public class PackstrasseBarcodeReaderModule : IVisionBuilderModule + { + private readonly PackstrasseBarcodeReaderSettings _settings; + private readonly IRecognitionControl _recognitionControl; + private SerialPort _port; + + + public PackstrasseBarcodeReaderModule(PackstrasseBarcodeReaderSettings settings, IRecognitionControl recognitionControl) + { + _settings = settings; + _recognitionControl = recognitionControl; + } + + private void _port_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + Thread.Sleep(100); + byte[] buffer = new byte[_port.BytesToRead]; + _port.Read(buffer, 0, _port.BytesToRead); + var data = Encoding.ASCII.GetString(buffer).Trim(); + var recipes = _recognitionControl.GetRecipesData(); + + string searchString = data; + if (_settings.BarcodeRecipeMappings.Any(x=>x.Barcode==searchString)) + { + searchString = _settings.BarcodeRecipeMappings.First(x => x.Barcode == searchString).RecipeName; + } + + var existingRecipe=recipes.FirstOrDefault(x => x.RecipeName == searchString); + if (existingRecipe == null) + { + Log.Warning("No matching recipe found for data: {Data}", data); + return; + } + + _recognitionControl.SetRecipe(existingRecipe); + } + + + public void InitializeModule() + { + try + { + _port = new SerialPort(_settings.ComPort, 9600); + _port.Open(); + _port.DataReceived += _port_DataReceived; + } + catch (Exception e) + { + Log.Warning(e, "Failed to open com-port"); + } + + } + } +} diff --git a/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderSettings.cs b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderSettings.cs new file mode 100644 index 0000000..f29fd3d --- /dev/null +++ b/Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderSettings.cs @@ -0,0 +1,63 @@ +using Inspectron.Settings; +using System.Text.Json; +using System.Text.Json.Serialization; +using VisionBuilder.UI.Common; + +namespace PackstrasseBarcodeReader; + +public class PackstrasseBarcodeReaderSettings(string CameraName):ISettings +{ + + public string ComPort { get; set; }="COM3"; + + public List BarcodeRecipeMappings { get; set; } = new List(); + + public void RegisterSettings(InspectronSettings settings) + { + settings.RegisterSimple(this, () => ComPort, CameraName+"/Barcode reader", nameof(ComPort)); + settings.RegisterSimple(this, () => BarcodeRecipeMappings, CameraName+"/Barcode reader", nameof(BarcodeRecipeMappings)); + } +} + +public class BarcodeRecipeMapping +{ + public string Barcode { get; set; } + public string RecipeName { get; set; } +} + +public class ListBarcodeRecipeMappingConverter: ITypeConverter +{ + public object ConvertFrom(object value) + { + if (value is string json) + { + try + { + return JsonSerializer.Deserialize>(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter() } + }); + } + catch (JsonException) + { + throw new InvalidOperationException("Failed to deserialize BarcodeRecipeMapping list from JSON."); + } + } + throw new InvalidOperationException("Value must be a JSON string."); + } + + public object ConvertTo(object value, Type destinationType) + { + if (value is List list && destinationType == typeof(string)) + { + return JsonSerializer.Serialize(list, new JsonSerializerOptions + { + WriteIndented = false, + Converters = { new JsonStringEnumConverter() } + }); + } + throw new InvalidOperationException("Value must be a List and destinationType must be string."); + + } +} \ No newline at end of file diff --git a/Plugins/Pralinen/PackstrasseBarcodeReader/Plugin.cs b/Plugins/Pralinen/PackstrasseBarcodeReader/Plugin.cs new file mode 100644 index 0000000..9a034dc --- /dev/null +++ b/Plugins/Pralinen/PackstrasseBarcodeReader/Plugin.cs @@ -0,0 +1,21 @@ +using Inspectron.Settings; +using Ninject; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Plugins; + +namespace PackstrasseBarcodeReader; + +public class Plugin : IPlugin +{ + public void RegisterGlobalModules(IKernel kernel) + { + TypeConverterRegistry.Register>(new ListBarcodeRecipeMappingConverter()); + } + + public void RegisterCameraModules(IKernel kernel, string cameraName) + { + kernel.Bind() + .ToConstant(new PackstrasseBarcodeReaderSettings(cameraName)); + kernel.RegisterModule(); + } +} \ No newline at end of file diff --git a/Plugins/Siemens/B24SiemensPlugin/B24SiemensPlugin.csproj b/Plugins/Siemens/B24SiemensPlugin/B24SiemensPlugin.csproj index dc15c99..10f0a0a 100644 --- a/Plugins/Siemens/B24SiemensPlugin/B24SiemensPlugin.csproj +++ b/Plugins/Siemens/B24SiemensPlugin/B24SiemensPlugin.csproj @@ -5,7 +5,7 @@ enable enable true - D:\Inspectron\Hawkeye\code\VisionBuilder5\VisionBuilder.UI\VisionBuilder.UI.Windows.Test\bin\Debug\Data\Plugins\B24SiemensPlugin + ..\..\..\VisionBuilder.UI.Windows.Test\bin\Debug\Data\Plugins\B24SiemensPlugin diff --git a/VisionBuilder.UI.Camera/ModuleExtensions.cs b/VisionBuilder.UI.Camera/ModuleExtensions.cs index 9fdd331..70ddb24 100644 --- a/VisionBuilder.UI.Camera/ModuleExtensions.cs +++ b/VisionBuilder.UI.Camera/ModuleExtensions.cs @@ -14,6 +14,7 @@ public static class ModuleExtensions { self.Bind().ToConstant(new CameraSettings(cameraName)); self.Bind().ToConstant(new EmulationSettings(cameraName)); + self.Bind().ToConstant(new HawkeyeSettings(cameraName)); self.Bind().ToConstant(new IDSImageSourceSettings(cameraName)); self.Bind().ToSelf().InSingletonScope(); return self; diff --git a/VisionBuilder.UI.Common/Services/IPasswordInputService.cs b/VisionBuilder.UI.Common/Services/IPasswordInputService.cs new file mode 100644 index 0000000..01c9db8 --- /dev/null +++ b/VisionBuilder.UI.Common/Services/IPasswordInputService.cs @@ -0,0 +1,6 @@ +namespace VisionBuilder.UI.Common.Services; + +public interface IPasswordInputService +{ + string GetPassword(); +} \ No newline at end of file diff --git a/VisionBuilder.UI.Common/UIConfiguration.cs b/VisionBuilder.UI.Common/UIConfiguration.cs index 36ba3c8..1283c33 100644 --- a/VisionBuilder.UI.Common/UIConfiguration.cs +++ b/VisionBuilder.UI.Common/UIConfiguration.cs @@ -14,13 +14,16 @@ public class UIConfiguration: ISettings public string AdminPassword { get; set; } = ""; + public bool ProtectSettingsWithPassword { get; set; }=false; + public List ErrorShortNames { get; set; } = new List(); public void RegisterSettings(InspectronSettings settings) { - settings.RegisterSimple(this, () => this.MaxErrorCount, "Errors", "Max errors count", "UI"); - settings.RegisterSimple(this, () => this.AdminPassword, "System", "Password", "UI"); - settings.RegisterSimple(this, () => this.ErrorShortNames, "Errors", "Error short names", "UI"); + settings.RegisterSimple(this, () => this.MaxErrorCount, "Errors", "Max errors count"); + settings.RegisterSimple(this, () => this.AdminPassword, "System", "Password"); + settings.RegisterSimple(this, () => this.ProtectSettingsWithPassword, "System", nameof(ProtectSettingsWithPassword)); + settings.RegisterSimple(this, () => this.ErrorShortNames, "Errors", "Error short names"); } diff --git a/VisionBuilder.UI.IOCommander.Windows/VisionBuilder.UI.IOCommander.Windows.csproj b/VisionBuilder.UI.IOCommander.Windows/VisionBuilder.UI.IOCommander.Windows.csproj index b3d4eda..c65d236 100644 --- a/VisionBuilder.UI.IOCommander.Windows/VisionBuilder.UI.IOCommander.Windows.csproj +++ b/VisionBuilder.UI.IOCommander.Windows/VisionBuilder.UI.IOCommander.Windows.csproj @@ -9,6 +9,7 @@ + diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/EmptyImageSource.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/EmptyImageSource.cs new file mode 100644 index 0000000..6753147 --- /dev/null +++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/EmptyImageSource.cs @@ -0,0 +1,13 @@ +using OpenCvSharp; +using VisionBuilder.UI.Common.RecipeProcessing; + +namespace VisionBuilder.UI.Recipes.HawkeyeRecipe; + +public class EmptyImageSource: IImageSource +{ + + public Task GetImage(CancellationToken token) + { + return Task.FromResult(new Mat(480, 640, MatType.CV_8UC3, Scalar.Black)); + } +} \ No newline at end of file diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs index 715681b..0ed816d 100644 --- a/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs +++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/HawkeyeRecognitionControl.cs @@ -1,8 +1,11 @@ -using System.Diagnostics; +using System.CodeDom; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Hawkeye.VisionBuilder.Workflow; using Hawkeye.VisionBuilder.Workflow.Datatypes; +using Hawkeye.VisionBuilder.Workflow.Operations; +using Serilog; using VisionBuilder.UI.Common.Commands; using VisionBuilder.UI.Common.RecipeProcessing; using VisionBuilder.UI.Common.ViewModel.Classes; @@ -86,6 +89,25 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe _startTime = DateTime.Now; IsRunning = true; _cts = new CancellationTokenSource(); + + + Log.Information($"Warming up"); + try + { + var swWarmup = Stopwatch.StartNew(); + // warmup run + _workflow.ImageSource = new EmptyImageSource(); + _workflow.Context = new Context(); + _workflow.Context.CancellationToken = CancellationToken.None; + _workflow.Execute(); // Warmup run + 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 @@ -174,11 +196,19 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe var annotated = _workflow.Context.ActiveImage.ImageData.Clone(); var canvas = new OpenCVCanvas(annotated); _workflow.Context.GraphicsElements.ForEach(x => x.Draw(canvas)); + + TimeSpan cameraTime = TimeSpan.FromMilliseconds(0); + var cameraOperation = _workflow.Operations.FirstOrDefault(x => x is GetImageOperation); + if (cameraOperation != null) + { + cameraTime=cameraOperation.ExecutionTime; + } + var processingResult = new ImageProcessedEvent() { SessionStart = _startTime, RecipeName = _currentRecipe.RecipeName, - AnalysisTime = sw.Elapsed, + AnalysisTime = sw.Elapsed- cameraTime, ErrorNames = errorNames.ToList(), HasError = errorNames.Length > 0, ImageSource = _recognitionConfiguration.CameraName, @@ -186,7 +216,10 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe ImageAnalysis = annotated }; + var swProcessed = Stopwatch.StartNew(); ImageProcessed(processingResult); + swProcessed.Stop(); + Log.Information($"ImageProcessed event handled in {swProcessed.ElapsedMilliseconds} ms"); } diff --git a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs index f351bfc..d7da130 100644 --- a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs +++ b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs @@ -2,6 +2,7 @@ using Inspectron.Fastbuffer.Filesystems; using Lindt.Colorballs.Duo.Utils; using OpenCvSharp; +using Serilog; using VisionBuilder.UI.Common; using VisionBuilder.UI.Common.Commands; using VisionBuilder.UI.Common.RecipeProcessing; @@ -12,26 +13,58 @@ public class VisionBuilderRingbuffer:IVisionBuilderModule { private readonly VisionBuilderRingbufferSettings _settings; private readonly IRecognitionControl _recognitionControl; + private readonly ILoadingService _loadingService; - public VisionBuilderRingbuffer(VisionBuilderRingbufferSettings settings, IRecognitionControl recognitionControl) + public VisionBuilderRingbuffer(VisionBuilderRingbufferSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService) { _settings = settings; _recognitionControl = recognitionControl; - + _loadingService = loadingService; } public void InitializeModule() { _recognitionControl.SessionStarted += _recognitionControl_SessionStarted; + _recognitionControl.SessionEnded += _recognitionControl_SessionEnded; _recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed; } + + private void _recognitionControl_SessionEnded(SessionEndedEvent obj) + { + _loadingService.StartLoading("Processing ringbuffer"); + Log.Information("Processing ringbuffer"); + try + { + CleanUp(); + } + catch (Exception e) + { + + } + finally + { + Log.Information("Finished processing ringbuffer"); + _loadingService.StopLoading("Processing ringbuffer"); + } + } + private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj) { - if (obj.HasError) + var sw = System.Diagnostics.Stopwatch.StartNew(); + try { - WriteBadResult(obj); - return; + if (obj.HasError) + { + WriteBadResult(obj); + return; + } + WriteGoodResult(obj); } - WriteGoodResult(obj); + finally + { + sw.Stop(); + Console.WriteLine($"Ringbuffer: wrote image in {sw.ElapsedMilliseconds}ms"); + } + } private void _recognitionControl_SessionStarted(VisionBuilder.UI.Common.Commands.SessionStartedEvent obj) @@ -43,11 +76,20 @@ public class VisionBuilderRingbuffer:IVisionBuilderModule private string _path; private DateTime _startTime; private string _product; - + + private void CleanUp() + { + _goodRingbufferIsolated?.Dispose(); + foreach (var ringbuffer in _badRingbuffers) + { + ringbuffer.Value.Dispose(); + } + _badRingbuffers.Clear(); + } public void Dispose() { - + CleanUp(); } @@ -58,8 +100,7 @@ public class VisionBuilderRingbuffer:IVisionBuilderModule try { var image = result.ImageOriginal; - // save to memory stream - _goodRingbufferIsolated.Write(image.ToBytes(), filename); + _goodRingbufferIsolated.Write(image, filename); } catch (Exception e) { @@ -87,9 +128,7 @@ public class VisionBuilderRingbuffer:IVisionBuilderModule var ringbuffer = GetBadRingbuffer(defectName); try { - - - ringbuffer.Write(image.ToBytes(), filename); + ringbuffer.Write(image, filename); } catch (Exception e) { @@ -116,15 +155,10 @@ public class VisionBuilderRingbuffer:IVisionBuilderModule _startTime = DateTime.Now; _product = product; _path = PathSettingsUtils.ConvertVariables(Path.Combine(_settings.Root,_settings.GoodImagesPathTemplate), _startTime, _startTime, _product,cameraName:_settings.CameraName); - _goodRingbufferIsolated?.Dispose(); + _goodRingbufferIsolated = new IsolatedRingbuffer(_path, _settings.MaxGood, new AsyncFilesystem()); - foreach (var ringbuffer in _badRingbuffers) - { - ringbuffer.Value.Dispose(); - } - - _badRingbuffers.Clear(); + } diff --git a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs index 686a40e..ec0d881 100644 --- a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs +++ b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs @@ -31,12 +31,12 @@ Specifies directory, in which MaxBad and MaxGood are applied. [SettingDescription("Specifies directory, relative to the Root")] [SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))] - public string GoodImagesPathTemplate { get; set; } = @"\$RECIPE_NAME"; + public string GoodImagesPathTemplate { get; set; } = @"$RECIPE_NAME"; [SettingDescription("Specifies directory, relative to the Root")] [SettingPreview(typeof(VisionBuilderRingbufferSettings),nameof(ImagesPathTemplatePreview))] public string BadImagesPathTemplate { get; set; } = - @"\$RECIPE_NAME\$DEFECT_NAME"; + @"$RECIPE_NAME\$DEFECT_NAME"; [SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))] diff --git a/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs b/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs index 3aa90b9..2da909b 100644 --- a/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs +++ b/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs @@ -3,6 +3,7 @@ using Lindt.Colorballs.Duo.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection.PortableExecutable; using Ninject; @@ -44,6 +45,7 @@ namespace VisionBuilder.UI.Statistics { var endedAt = obj.SessionEnded; _endTime = endedAt; + Log.Information("Start processing statistics"); _loadingService.StartLoading("Writing statistics for " + _settings.CameraName); try { @@ -56,15 +58,19 @@ namespace VisionBuilder.UI.Statistics finally { _loadingService.StopLoading("Writing statistics for " + _settings.CameraName); + Log.Information("End processing statistics"); } } private void _recognitionControl_ImageProcessed(ImageProcessedEvent obj) { + var sw = Stopwatch.StartNew(); if (obj.HasError) { RecordBadImage(obj.ErrorNames[0]); } + sw.Stop(); + Console.WriteLine($"Recording statistics took {sw.ElapsedMilliseconds} ms"); } private void _recognitionControl_SessionStarted(SessionStartedEvent obj) diff --git a/VisionBuilder.UI.Windows.Test/AppSettings.cs b/VisionBuilder.UI.Windows.Test/AppSettings.cs deleted file mode 100644 index 51b4ec9..0000000 --- a/VisionBuilder.UI.Windows.Test/AppSettings.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Inspectron.Settings; - -namespace VisionBuilder.UI.Windows.Test; - -public class AppSettings -{ - -} \ No newline at end of file diff --git a/VisionBuilder.UI.Windows.Test/Form1.cs b/VisionBuilder.UI.Windows.Test/Form1.cs index 42a6cfa..0651c25 100644 --- a/VisionBuilder.UI.Windows.Test/Form1.cs +++ b/VisionBuilder.UI.Windows.Test/Form1.cs @@ -1,5 +1,10 @@ +using System.Runtime.InteropServices; using MaterialSkin; using MaterialSkin.Controls; +using Microsoft.VisualBasic.Logging; +using OpenCvSharp; +using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Services; using VisionBuilder.UI.Common.ViewModel; namespace VisionBuilder.UI.Windows.Test @@ -7,10 +12,14 @@ namespace VisionBuilder.UI.Windows.Test public partial class Form1 : MaterialForm { private readonly MainWindowVM _mainWindowVm; + private readonly IPasswordInputService _passwordInputService; + private readonly UIConfiguration _uiConfiguration; - public Form1(MainWindowVM mainWindowVm) + public Form1(MainWindowVM mainWindowVm, IPasswordInputService passwordInputService, UIConfiguration uiConfiguration) { _mainWindowVm = mainWindowVm; + _passwordInputService = passwordInputService; + _uiConfiguration = uiConfiguration; MaterialSkin.MaterialSkinManager.ConfigureForInspectron(); InitializeComponent(); singleCameraControl1.SetViewModel(mainWindowVm.SingleCameraVms[0]); @@ -54,6 +63,12 @@ namespace VisionBuilder.UI.Windows.Test private void btnSettings_Click(object sender, EventArgs e) { + if (_uiConfiguration.ProtectSettingsWithPassword) + { + var passwordOk = _passwordInputService.GetPassword() == _uiConfiguration.AdminPassword; + if (!passwordOk) + return; + } _mainWindowVm.SettingsCommand.Execute(null); } diff --git a/VisionBuilder.UI.Windows.Test/Inspectron icon-512.ico b/VisionBuilder.UI.Windows.Test/Inspectron icon-512.ico new file mode 100644 index 0000000..19e6370 Binary files /dev/null and b/VisionBuilder.UI.Windows.Test/Inspectron icon-512.ico differ diff --git a/VisionBuilder.UI.Windows.Test/Program.cs b/VisionBuilder.UI.Windows.Test/Program.cs index 6b6a510..84391c6 100644 --- a/VisionBuilder.UI.Windows.Test/Program.cs +++ b/VisionBuilder.UI.Windows.Test/Program.cs @@ -97,9 +97,11 @@ namespace VisionBuilder.UI.Windows.Test kernelCamera1.Get() ]; + var form = mainKernel.Get(); settings.LoadSettings(); - Application.Run(new Form1(mainWindowVm)); + + Application.Run(form); } } diff --git a/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj b/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Uno.csproj similarity index 93% rename from VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj rename to VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Uno.csproj index 5b142f6..72efc3f 100644 --- a/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj +++ b/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Uno.csproj @@ -6,8 +6,13 @@ enable true enable + Inspectron icon-512.ico + + + + diff --git a/VisionBuilder.UI.Windows/Components/SingleCameraControl.Designer.cs b/VisionBuilder.UI.Windows/Components/SingleCameraControl.Designer.cs index e17a669..395b5ca 100644 --- a/VisionBuilder.UI.Windows/Components/SingleCameraControl.Designer.cs +++ b/VisionBuilder.UI.Windows/Components/SingleCameraControl.Designer.cs @@ -51,7 +51,7 @@ lblCameraName.Font = new Font("Segoe UI Semibold", 12F, FontStyle.Bold, GraphicsUnit.Point, 204); lblCameraName.Location = new Point(8, 8); lblCameraName.Name = "lblCameraName"; - lblCameraName.Size = new Size(616, 21); + lblCameraName.Size = new Size(744, 21); lblCameraName.TabIndex = 2; lblCameraName.Text = "CameraName"; lblCameraName.TextAlign = ContentAlignment.TopCenter; @@ -60,7 +60,7 @@ // label1.Anchor = AnchorStyles.Top | AnchorStyles.Right; label1.Font = new Font("Segoe UI", 12F); - label1.Location = new Point(656, 8); + label1.Location = new Point(784, 0); label1.Name = "label1"; label1.Size = new Size(408, 21); label1.TabIndex = 4; @@ -72,7 +72,7 @@ previewWindow1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; previewWindow1.Location = new Point(0, 32); previewWindow1.Name = "previewWindow1"; - previewWindow1.Size = new Size(624, 456); + previewWindow1.Size = new Size(752, 456); previewWindow1.SizeMode = PictureBoxSizeMode.Zoom; previewWindow1.TabIndex = 5; previewWindow1.TabStop = false; @@ -82,7 +82,7 @@ materialDivider1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; materialDivider1.BackColor = Color.FromArgb(55, 71, 79); materialDivider1.Depth = 0; - materialDivider1.Location = new Point(632, 32); + materialDivider1.Location = new Point(760, 24); materialDivider1.MouseState = MaterialSkin.MouseState.HOVER; materialDivider1.Name = "materialDivider1"; materialDivider1.Size = new Size(1, 456); @@ -92,7 +92,7 @@ // errorPreview1 // errorPreview1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; - errorPreview1.Location = new Point(648, 32); + errorPreview1.Location = new Point(776, 24); errorPreview1.Name = "errorPreview1"; errorPreview1.Size = new Size(416, 456); errorPreview1.TabIndex = 7; @@ -101,9 +101,9 @@ // stats1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; stats1.BackColor = Color.White; - stats1.Location = new Point(1080, 32); + stats1.Location = new Point(1200, 32); stats1.Name = "stats1"; - stats1.Size = new Size(320, 456); + stats1.Size = new Size(232, 456); stats1.TabIndex = 8; // // btnStart @@ -128,11 +128,11 @@ flowLayoutPanel1.Controls.Add(flowLayoutPanel2); flowLayoutPanel1.Dock = DockStyle.Right; flowLayoutPanel1.FlowDirection = FlowDirection.TopDown; - flowLayoutPanel1.Location = new Point(1408, 0); + flowLayoutPanel1.Location = new Point(1448, 0); flowLayoutPanel1.Name = "flowLayoutPanel1"; flowLayoutPanel1.Padding = new Padding(8, 24, 8, 8); flowLayoutPanel1.RightToLeft = RightToLeft.Yes; - flowLayoutPanel1.Size = new Size(280, 498); + flowLayoutPanel1.Size = new Size(240, 498); flowLayoutPanel1.TabIndex = 10; // // flowLayoutPanel2 @@ -140,7 +140,7 @@ flowLayoutPanel2.Controls.Add(btnSelectRecipe); flowLayoutPanel2.Controls.Add(btnStart); flowLayoutPanel2.Controls.Add(btnStop); - flowLayoutPanel2.Location = new Point(61, 27); + flowLayoutPanel2.Location = new Point(21, 27); flowLayoutPanel2.Name = "flowLayoutPanel2"; flowLayoutPanel2.Size = new Size(200, 237); flowLayoutPanel2.TabIndex = 12; @@ -184,7 +184,7 @@ materialDivider2.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; materialDivider2.BackColor = Color.FromArgb(55, 71, 79); materialDivider2.Depth = 0; - materialDivider2.Location = new Point(1064, 32); + materialDivider2.Location = new Point(1192, 24); materialDivider2.MouseState = MaterialSkin.MouseState.HOVER; materialDivider2.Name = "materialDivider2"; materialDivider2.Size = new Size(1, 456); diff --git a/VisionBuilder.UI.Windows/ModuleExtensions.cs b/VisionBuilder.UI.Windows/ModuleExtensions.cs index f1fb4a8..9130e2f 100644 --- a/VisionBuilder.UI.Windows/ModuleExtensions.cs +++ b/VisionBuilder.UI.Windows/ModuleExtensions.cs @@ -1,5 +1,6 @@ using Ninject; using VisionBuilder.UI.Common.RecipeProcessing; +using VisionBuilder.UI.Common.Services; using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; using VisionBuilder.UI.Windows.Settings; @@ -13,6 +14,7 @@ public static class ModuleExtensions self.Bind().To().InSingletonScope(); self.Bind().To().InSingletonScope(); self.Bind().To().InSingletonScope(); + self.Bind().To().InSingletonScope(); return self; } } \ No newline at end of file diff --git a/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs b/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs new file mode 100644 index 0000000..3f876d9 --- /dev/null +++ b/VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs @@ -0,0 +1,19 @@ +using MaterialSkin.Core.Controls; +using VisionBuilder.UI.Common.Services; + +namespace VisionBuilder.UI.Windows.Settings; + +public class WindowsPasswordInputService: IPasswordInputService +{ + public string GetPassword() + { + if (MaterialInputBox.Prompt("Password required", "", out var password, true) == DialogResult.OK) + { + return password; + } + else + { + return null; + } + } +} \ No newline at end of file diff --git a/VisionBuilder.UI.sln b/VisionBuilder.UI.sln index fba4fc1..5a64f3a 100644 --- a/VisionBuilder.UI.sln +++ b/VisionBuilder.UI.sln @@ -11,7 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Windows", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialSkin.Core", "framework\MaterialSkin.Core\MaterialSkin.Core.csproj", "{D5FD2E9D-DA4F-1343-47E0-FBC473A149BC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Windows.Test", "VisionBuilder.UI.Windows.Test\VisionBuilder.UI.Windows.Test.csproj", "{7697A266-A721-4D74-9C5C-0D4F2F6BBF68}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Windows.Uno", "VisionBuilder.UI.Windows.Test\VisionBuilder.UI.Windows.Uno.csproj", "{7697A266-A721-4D74-9C5C-0D4F2F6BBF68}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.Settings", "framework\Inspectron.Settings\Inspectron.Settings.csproj", "{E286CE4C-B68A-94B6-F477-0DBF42358009}" EndProject @@ -87,6 +87,10 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "PythonModelAPI", "PythonMod EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PralinenPLC", "Plugins\Pralinen\PralinenPLC\PralinenPLC.csproj", "{19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackstrasseBarcodeReader", "Plugins\Pralinen\PackstrasseBarcodeReader\PackstrasseBarcodeReader.csproj", "{B20D7E75-1C19-632D-21D2-2663B0329C5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandyboxPlugin", "Plugins\CandyboxPlugin\CandyboxPlugin.csproj", "{31C47198-DEC2-4073-A0C2-220549502CD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -223,6 +227,14 @@ Global {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Debug|Any CPU.Build.0 = Debug|Any CPU {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.ActiveCfg = Release|Any CPU {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A}.Release|Any CPU.Build.0 = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B20D7E75-1C19-632D-21D2-2663B0329C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31C47198-DEC2-4073-A0C2-220549502CD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -256,6 +268,8 @@ Global {44D1BA17-FB52-40A2-9D99-E49DA56C10C2} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1} {B4949493-C2CD-4786-860D-DAACD39A662D} = {9FA47F4A-8F44-4F55-B6F7-99C962E9F18F} {19E9B1A2-FBC6-2668-EDDF-1B3A6828184A} = {583A77DF-A293-4F3E-AB96-7310BC495822} + {B20D7E75-1C19-632D-21D2-2663B0329C5E} = {583A77DF-A293-4F3E-AB96-7310BC495822} + {31C47198-DEC2-4073-A0C2-220549502CD1} = {583A77DF-A293-4F3E-AB96-7310BC495822} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677} diff --git a/framework/Inspectron.Fastbuffer/Filesystems/AsyncFilesystem.cs b/framework/Inspectron.Fastbuffer/Filesystems/AsyncFilesystem.cs index 822cf25..89591db 100644 --- a/framework/Inspectron.Fastbuffer/Filesystems/AsyncFilesystem.cs +++ b/framework/Inspectron.Fastbuffer/Filesystems/AsyncFilesystem.cs @@ -1,13 +1,15 @@ using Inspectron.Fastbuffer.Interfaces; using System.Collections.Concurrent; +using OpenCvSharp; +using Serilog; namespace Inspectron.Fastbuffer.Filesystems; public class AsyncFilesystem : IFilesystem { private readonly DirectFilesystem _directFilesystem = new(); - private readonly ConcurrentDictionary _buffer = new(); - private readonly BlockingCollection<(string path, byte[] data)> _writeQueue = new(); + private readonly List _buffer = new(); + private readonly BlockingCollection<(string path, Mat data)> _writeQueue = new(); private readonly Thread _backgroundThread; private readonly object _lock = new(); @@ -18,27 +20,17 @@ public class AsyncFilesystem : IFilesystem _backgroundThread.Start(); } - public void Write(string path, byte[] data) + public void Write(string path, Mat data) { lock (_lock) { - _buffer[path] = data; + _buffer.Add(path); _writeQueue.Add((path, data)); + } } - public byte[] Read(string path) - { - lock (_lock) - { - if (_buffer.TryGetValue(path, out var data)) - { - return data; - } - } - return _directFilesystem.Read(path); - } public void Delete(string path) { @@ -46,8 +38,9 @@ public class AsyncFilesystem : IFilesystem lock (_lock) { // pats is in buffer, so it's not written to disk yet - if (_buffer.TryRemove(path, out _)) + if (_buffer.Contains(path)) { + _buffer.Remove(path); return; } _directFilesystem.Delete(path); @@ -61,8 +54,9 @@ public class AsyncFilesystem : IFilesystem { _directFilesystem.Write(path, data); lock (_lock) - _buffer.TryRemove(path, out _); - + _buffer.Remove(path); + if (_writeQueue.Count > 5) + Log.Warning($"AsyncFilesystem write queue size: {_writeQueue.Count}"); } } diff --git a/framework/Inspectron.Fastbuffer/Filesystems/DirectFilesystem.cs b/framework/Inspectron.Fastbuffer/Filesystems/DirectFilesystem.cs index 23ae172..d0cd354 100644 --- a/framework/Inspectron.Fastbuffer/Filesystems/DirectFilesystem.cs +++ b/framework/Inspectron.Fastbuffer/Filesystems/DirectFilesystem.cs @@ -1,15 +1,17 @@ using Inspectron.Fastbuffer.Interfaces; +using OpenCvSharp; using Serilog; namespace Inspectron.Fastbuffer.Filesystems; public class DirectFilesystem:IFilesystem { - public void Write(string path, byte[] data) + public void Write(string path, Mat data) { // ensure path Directory.CreateDirectory(Path.GetDirectoryName(path)); - File.WriteAllBytes(path, data); + var bytes = data.ToBytes(); + File.WriteAllBytes(path, bytes); } public byte[] Read(string path) diff --git a/framework/Inspectron.Fastbuffer/IndexFile.cs b/framework/Inspectron.Fastbuffer/IndexFile.cs index af805fc..e7bdf16 100644 --- a/framework/Inspectron.Fastbuffer/IndexFile.cs +++ b/framework/Inspectron.Fastbuffer/IndexFile.cs @@ -1,5 +1,6 @@ using System.Text; using Inspectron.Fastbuffer.Interfaces; +using OpenCvSharp; using Serilog; namespace Inspectron.Fastbuffer; @@ -112,7 +113,7 @@ public class IndexFile } - public void WriteNextArtifact(byte[] data, string fileName) + public void WriteNextArtifact(Mat data, string fileName) { var id = GetNextId(); Log.Debug("Writing artifact {id}",id); @@ -132,19 +133,5 @@ public class IndexFile fileStream.Write(padding, 0, padding.Length); } - public byte[] ReadArtifact(int id) - { - using var fileStream = File.Open(_indexFilePath, FileMode.Open); - fileStream.Seek(id * RecordSize+ HeaderSize, SeekOrigin.Begin); - var bytes = new byte[RecordSize]; - fileStream.Read(bytes, 0, RecordSize); - var fileName = Encoding.UTF8.GetString(bytes).TrimEnd('\0'); - var filePath = Path.Combine(_bufferPath, fileName); - return _filesystem.Read(filePath); - } - - - - } \ No newline at end of file diff --git a/framework/Inspectron.Fastbuffer/Inspectron.Fastbuffer.csproj b/framework/Inspectron.Fastbuffer/Inspectron.Fastbuffer.csproj index 4d752c1..3fc3a94 100644 --- a/framework/Inspectron.Fastbuffer/Inspectron.Fastbuffer.csproj +++ b/framework/Inspectron.Fastbuffer/Inspectron.Fastbuffer.csproj @@ -11,6 +11,7 @@ + diff --git a/framework/Inspectron.Fastbuffer/Interfaces/IFilesystem.cs b/framework/Inspectron.Fastbuffer/Interfaces/IFilesystem.cs index 8e6a358..a227339 100644 --- a/framework/Inspectron.Fastbuffer/Interfaces/IFilesystem.cs +++ b/framework/Inspectron.Fastbuffer/Interfaces/IFilesystem.cs @@ -1,8 +1,9 @@ -namespace Inspectron.Fastbuffer.Interfaces; +using OpenCvSharp; + +namespace Inspectron.Fastbuffer.Interfaces; public interface IFilesystem:IDisposable { - public void Write(string path, byte[] data); - public byte[] Read(string path); + public void Write(string path, Mat data); public void Delete(string path); } \ No newline at end of file diff --git a/framework/Inspectron.Fastbuffer/IsolatedRingbuffer.cs b/framework/Inspectron.Fastbuffer/IsolatedRingbuffer.cs index b06ad3c..b79684b 100644 --- a/framework/Inspectron.Fastbuffer/IsolatedRingbuffer.cs +++ b/framework/Inspectron.Fastbuffer/IsolatedRingbuffer.cs @@ -1,4 +1,5 @@ using Inspectron.Fastbuffer.Interfaces; +using OpenCvSharp; namespace Inspectron.Fastbuffer; @@ -26,7 +27,7 @@ public class IsolatedRingbuffer: IDisposable } - public void Write(byte[] data, string fileName) + public void Write(Mat data, string fileName) { _index.WriteNextArtifact(data, fileName); } diff --git a/framework/Inspectron.Fastbuffer/RingbufferRepository.cs b/framework/Inspectron.Fastbuffer/RingbufferRepository.cs deleted file mode 100644 index a0c79c8..0000000 --- a/framework/Inspectron.Fastbuffer/RingbufferRepository.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Inspectron.Fastbuffer.Interfaces; - -namespace Inspectron.Fastbuffer -{ - public class RingbufferRepository - { - private readonly string _path; - - private readonly string _goodIndexPath; - private readonly string _badIndexPath; - private readonly IndexFile _goodIndex; - private readonly IndexFile _badIndex; - public const string BufferDirectory = ".rb"; - public const string GoodIndexFile = "good.idx"; - public const string BadIndexFile = "bad.idx"; - public RingbufferRepository(string path, int goodBufferSize, int badBufferSize, IFilesystem filesystem) - { - _path = path; - - _goodIndexPath = Path.Combine(_path, BufferDirectory, GoodIndexFile); - _badIndexPath = Path.Combine(_path, BufferDirectory, BadIndexFile); - Directory.CreateDirectory(Path.Combine(_path,BufferDirectory)); - _goodIndex = new IndexFile(_goodIndexPath, goodBufferSize, filesystem); - _badIndex = new IndexFile(_badIndexPath, badBufferSize, filesystem); - - var id= _goodIndex.GetCurrentArtifactId(); - Console.WriteLine(@"RB size: "+id); - for (int i = 0; i < 3; i++) - { - var artifact = _goodIndex.GetArtifactPath(i); - Console.WriteLine(@"Artifact: "+artifact); - } - - } - - public IndexFile GoodIndex => _goodIndex; - - public IndexFile BadIndex => _badIndex; - - public void WriteGood(byte[] data, string fileName) - { - _goodIndex.WriteNextArtifact(data, fileName); - } - - public void WriteBad(byte[] data, string fileName) - { - _badIndex.WriteNextArtifact(data, fileName); - } - - - } -}