From e5746ef7662af3d3d4311438bbd9123e792ad3c6 Mon Sep 17 00:00:00 2001 From: meelstorm <8780022+TesAnti@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:11:23 +0200 Subject: [PATCH] long candy ready to test --- .../HawkeyeCameraImageSource.cs | 98 +++++++++---- .../HawkeyeSettings.cs | 3 + .../OpenCVBayerFilter.cs | 124 ++++++++++++++++ .../SplineInterpolator.cs | 138 ++++++++++++++++++ .../Operations/AI/OnnxModelOperation.cs | 136 +++++++++++++++++ .../Hawkeye.VisionBuilder.csproj | 1 + Hawkeye.VisionBuilder/MainWindow.cs | 1 + Plugins/CandyboxPlugin/CandyboxPlugin.csproj | 28 ++++ Plugins/CandyboxPlugin/Class1.cs | 7 + .../PackstrasseBarcodeReader.csproj | 27 ++++ .../PackstrasseBarcodeReaderModule.cs | 63 ++++++++ .../PackstrasseBarcodeReaderSettings.cs | 63 ++++++++ .../PackstrasseBarcodeReader/Plugin.cs | 21 +++ .../B24SiemensPlugin/B24SiemensPlugin.csproj | 2 +- VisionBuilder.UI.Camera/ModuleExtensions.cs | 1 + .../Services/IPasswordInputService.cs | 6 + VisionBuilder.UI.Common/UIConfiguration.cs | 9 +- ...isionBuilder.UI.IOCommander.Windows.csproj | 1 + .../EmptyImageSource.cs | 13 ++ .../HawkeyeRecognitionControl.cs | 37 ++++- .../VisionBuilderRingbuffer.cs | 74 +++++++--- .../VisionBuilderRingbufferSettings.cs | 4 +- .../VisionBuilderStatistics.cs | 6 + VisionBuilder.UI.Windows.Test/AppSettings.cs | 8 - VisionBuilder.UI.Windows.Test/Form1.cs | 17 ++- .../Inspectron icon-512.ico | Bin 0 -> 105844 bytes VisionBuilder.UI.Windows.Test/Program.cs | 4 +- ...oj => VisionBuilder.UI.Windows.Uno.csproj} | 5 + .../SingleCameraControl.Designer.cs | 22 +-- VisionBuilder.UI.Windows/ModuleExtensions.cs | 2 + .../Services/WindowsPasswordInputService.cs | 19 +++ VisionBuilder.UI.sln | 16 +- .../Filesystems/AsyncFilesystem.cs | 30 ++-- .../Filesystems/DirectFilesystem.cs | 6 +- framework/Inspectron.Fastbuffer/IndexFile.cs | 17 +-- .../Inspectron.Fastbuffer.csproj | 1 + .../Interfaces/IFilesystem.cs | 7 +- .../IsolatedRingbuffer.cs | 3 +- .../RingbufferRepository.cs | 52 ------- 39 files changed, 904 insertions(+), 168 deletions(-) create mode 100644 Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs create mode 100644 Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs create mode 100644 Hawkeye.VisionBuilder.Workflow/Operations/AI/OnnxModelOperation.cs create mode 100644 Plugins/CandyboxPlugin/CandyboxPlugin.csproj create mode 100644 Plugins/CandyboxPlugin/Class1.cs create mode 100644 Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReader.csproj create mode 100644 Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderModule.cs create mode 100644 Plugins/Pralinen/PackstrasseBarcodeReader/PackstrasseBarcodeReaderSettings.cs create mode 100644 Plugins/Pralinen/PackstrasseBarcodeReader/Plugin.cs create mode 100644 VisionBuilder.UI.Common/Services/IPasswordInputService.cs create mode 100644 VisionBuilder.UI.Recipes.HawkeyeRecipe/EmptyImageSource.cs delete mode 100644 VisionBuilder.UI.Windows.Test/AppSettings.cs create mode 100644 VisionBuilder.UI.Windows.Test/Inspectron icon-512.ico rename VisionBuilder.UI.Windows.Test/{VisionBuilder.UI.Windows.Test.csproj => VisionBuilder.UI.Windows.Uno.csproj} (93%) create mode 100644 VisionBuilder.UI.Windows/Services/WindowsPasswordInputService.cs delete mode 100644 framework/Inspectron.Fastbuffer/RingbufferRepository.cs 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 0000000000000000000000000000000000000000..19e637011e871b7883b28b84ba15ef74641d1d06 GIT binary patch literal 105844 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 GDf~Ytx62j) literal 0 HcmV?d00001 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); - } - - - } -}