ringbuffer and statistics
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using Ninject;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
|
||||
{
|
||||
public class EmulationCameraImageSource:IImageSource, IInitializable
|
||||
public class EmulationCameraImageSource:IImageSource, IVisionBuilderModule
|
||||
{
|
||||
private readonly EmulationSettings _settings;
|
||||
private readonly string _name;
|
||||
@@ -91,7 +92,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
|
||||
private int _index = 0;
|
||||
|
||||
|
||||
public void Initialize()
|
||||
public void InitializeModule()
|
||||
{
|
||||
if(string.IsNullOrEmpty(_settings.EmulationPath)) return;
|
||||
SetFolderAbsolute(_settings.EmulationPath);
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
41
VisionBuilder.UI.Camera/ModuleExtensions.cs
Normal file
41
VisionBuilder.UI.Camera/ModuleExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Hawkeye.VisionBuilder.UI.Sources.Emulation;
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace Lindt.Colorballs.Duo.Utils;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IChildKernel RegisterCamera(this IChildKernel self, string cameraName)
|
||||
{
|
||||
self.Bind<CameraSettings, ISettings>().ToConstant(new CameraSettings(cameraName));
|
||||
self.Bind<EmulationSettings, ISettings>().ToConstant(new EmulationSettings(cameraName));
|
||||
return self;
|
||||
}
|
||||
private static void BindCameras(CameraSettings cameraSettings, IChildKernel kernel)
|
||||
{
|
||||
switch (cameraSettings.CameraSource)
|
||||
{
|
||||
case CameraSettings.EImageSource.Emulation:
|
||||
|
||||
kernel.Bind<IImageSource, IVisionBuilderModule>().To<EmulationCameraImageSource>().InSingletonScope();
|
||||
break;
|
||||
case CameraSettings.EImageSource.Hawkeye:
|
||||
break;
|
||||
case CameraSettings.EImageSource.IDS:
|
||||
break;
|
||||
case CameraSettings.EImageSource.Basler:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
public static IChildKernel UseCamera(this IChildKernel self)
|
||||
{
|
||||
var cameraSettings = self.Get<CameraSettings>();
|
||||
BindCameras(cameraSettings, self);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
14
VisionBuilder.UI.Camera/VisionBuilder.UI.Camera.csproj
Normal file
14
VisionBuilder.UI.Camera/VisionBuilder.UI.Camera.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.Emulation\Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -20,12 +20,12 @@ public class CameraSettings : ISettings
|
||||
|
||||
}
|
||||
|
||||
public EImageSource Camera1Source { get; set; }
|
||||
public string Camera1Label { get; set; }
|
||||
public EImageSource CameraSource { get; set; }
|
||||
public string CameraLabel { get; set; }
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => this.Camera1Source, CameraName, "Image source");
|
||||
settings.RegisterSimple(this, () => this.Camera1Label, CameraName, "Label");
|
||||
settings.RegisterSimple(this, () => this.CameraSource, CameraName, "Image source");
|
||||
settings.RegisterSimple(this, () => this.CameraLabel, CameraName, "Label");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Inspectron.Settings;
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
|
||||
namespace VisionBuilder.UI.Common;
|
||||
|
||||
@@ -14,6 +15,29 @@ public static class Extensions
|
||||
{
|
||||
settingsInstance.RegisterSettings(self.Get<InspectronSettings>());
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterModule<T>(this IKernel self) where T : IVisionBuilderModule
|
||||
{
|
||||
self.Bind<T, IVisionBuilderModule>().To<T>().InSingletonScope();
|
||||
}
|
||||
|
||||
public static void InitializeModules(this IKernel self)
|
||||
{
|
||||
|
||||
var serviceTypes = self.GetAll(typeof(IVisionBuilderModule)).Select(x => (IVisionBuilderModule)x).ToList();
|
||||
foreach (var service in serviceTypes)
|
||||
{
|
||||
if (service != null)
|
||||
{
|
||||
service.InitializeModule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
6
VisionBuilder.UI.Common/IVisionBuilderModule.cs
Normal file
6
VisionBuilder.UI.Common/IVisionBuilderModule.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace VisionBuilder.UI.Common;
|
||||
|
||||
public interface IVisionBuilderModule
|
||||
{
|
||||
public void InitializeModule();
|
||||
}
|
||||
@@ -1,7 +1,32 @@
|
||||
namespace VisionBuilder.UI.Common;
|
||||
namespace Lindt.Colorballs.Duo.Utils;
|
||||
|
||||
public static class PathSettingsUtils
|
||||
{
|
||||
public const string INSTUCTIONS = @"
|
||||
You can use the following system variables:
|
||||
|
||||
$RECIPE_NAME - name of the current recipe
|
||||
$DEFECT_NAME - name of the current defect(for bad images)
|
||||
$CAMERA_NAME - name of the camera
|
||||
|
||||
$HOUR - hour of the image
|
||||
$MINUTE - minute of the image
|
||||
$SECOND - second of the image
|
||||
$MS - milliseconds of the image
|
||||
|
||||
$DAY - day of the image
|
||||
$MONTH_NUM - number of the month of the image
|
||||
$MONTH_NAME - name of the month of the image
|
||||
$YEAR - year of the image
|
||||
|
||||
$SS_HOUR - hour of the session start
|
||||
$SS_MINUTE - minute of the session start
|
||||
$SS_SECOND - second of the session start
|
||||
|
||||
$SS_DAY - day of the session start
|
||||
$SS_MONTH_NUM - number of the month of the session start
|
||||
$SS_MONTH_NAME - name of the month of the session start
|
||||
$SS_YEAR - year of the session start";
|
||||
public static string ConvertVariables(
|
||||
string text,
|
||||
DateTime imageTime,
|
||||
@@ -25,7 +50,8 @@ public static class PathSettingsUtils
|
||||
["$MS"] = imageTime.Millisecond.ToString("D3"),
|
||||
|
||||
["$DAY"] = imageTime.Day.ToString("D2"),
|
||||
["$MONTH"] = imageTime.Month.ToString("D2"),
|
||||
["$MONTH_NUM"] = imageTime.Month.ToString("D2"),
|
||||
["$MONTH_NAME"] = imageTime.ToString("MMMM"),
|
||||
["$YEAR"] = imageTime.Year.ToString(),
|
||||
|
||||
["$SS_HOUR"] = sessionTime.Hour.ToString("D2"),
|
||||
@@ -33,7 +59,8 @@ public static class PathSettingsUtils
|
||||
["$SS_SECOND"] = sessionTime.Second.ToString("D2"),
|
||||
|
||||
["$SS_DAY"] = sessionTime.Day.ToString("D2"),
|
||||
["$SS_MONTH"] = sessionTime.ToString("MMMM"),
|
||||
["$SS_MONTH_NUM"] = sessionTime.Month.ToString("D2"),
|
||||
["$SS_MONTH_NAME"] = sessionTime.ToString("MMMM"),
|
||||
["$SS_YEAR"] = sessionTime.Year.ToString()
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace VisionBuilder.UI.Common
|
||||
[NotifyCanExecuteChangedFor(nameof(StopCommand))]
|
||||
private bool _isRunning;
|
||||
[ObservableProperty]
|
||||
private string _cameraName;
|
||||
private string _cameraLabel;
|
||||
|
||||
|
||||
public bool CanStop => IsRunning;
|
||||
@@ -56,7 +56,7 @@ namespace VisionBuilder.UI.Common
|
||||
StatisticsVm = new StatisticsVM();
|
||||
PreviewVm = new PreviewVM();
|
||||
|
||||
CameraName = cameraSettings.CameraName;
|
||||
CameraLabel = cameraSettings.CameraLabel;
|
||||
|
||||
recognitionControl.ImageProcessed += Handle;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
23
VisionBuilder.UI.Common/VisionBuilder.cs
Normal file
23
VisionBuilder.UI.Common/VisionBuilder.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Inspectron.Settings;
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
|
||||
namespace VisionBuilder.UI.Common;
|
||||
|
||||
public static class VisionBuilder
|
||||
{
|
||||
public static IKernel CreateMainKernel(InspectronSettings settings)
|
||||
{
|
||||
StandardKernel mainKernel = new StandardKernel();
|
||||
|
||||
// GLOBAL SETTINGS //
|
||||
mainKernel.Bind<InspectronSettings>().ToConstant(settings);
|
||||
mainKernel.Bind<UIConfiguration, ISettings>().ToConstant(new UIConfiguration());
|
||||
mainKernel.Bind<ILearningTool>().To<NoLearning>().InSingletonScope();
|
||||
// --- //
|
||||
|
||||
return mainKernel;
|
||||
}
|
||||
|
||||
}
|
||||
15
VisionBuilder.UI.Console/ModuleExtensions.cs
Normal file
15
VisionBuilder.UI.Console/ModuleExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Console;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IChildKernel UseConsole(this IChildKernel self)
|
||||
{
|
||||
self.Bind<VisionBuilderConsoleSettings, ISettings>().ToConstant(new VisionBuilderConsoleSettings());
|
||||
self.RegisterModule<VisionBuilderConsole>();
|
||||
return self;
|
||||
}
|
||||
}
|
||||
17
VisionBuilder.UI.Console/VisionBuilder.UI.Console.csproj
Normal file
17
VisionBuilder.UI.Console/VisionBuilder.UI.Console.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
VisionBuilder.UI.Console/VisionBuilderConsole.cs
Normal file
36
VisionBuilder.UI.Console/VisionBuilderConsole.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Console
|
||||
{
|
||||
public class VisionBuilderConsole: IVisionBuilderModule
|
||||
{
|
||||
private readonly VisionBuilderConsoleSettings _settings;
|
||||
|
||||
public VisionBuilderConsole(VisionBuilderConsoleSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public void InitializeModule()
|
||||
{
|
||||
if (_settings.CreateConsoleWindow)
|
||||
{
|
||||
WinConsole.Initialize();
|
||||
}
|
||||
// configure Serilog to use console
|
||||
if (_settings.UseConsole)
|
||||
{
|
||||
Serilog.Log.Logger = new Serilog.LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.Console()
|
||||
.CreateLogger();
|
||||
|
||||
Log.Information("Console initialized");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
18
VisionBuilder.UI.Console/VisionBuilderConsoleSettings.cs
Normal file
18
VisionBuilder.UI.Console/VisionBuilderConsoleSettings.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Inspectron.Settings;
|
||||
using Inspectron.Settings.Attributes;
|
||||
using Serilog;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Console;
|
||||
|
||||
public class VisionBuilderConsoleSettings:ISettings
|
||||
{
|
||||
public bool UseConsole { get; set; } = false;
|
||||
[SettingDescription("Create a console window for non-console application")]
|
||||
public bool CreateConsoleWindow { get; set; }
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => this.UseConsole, "Console", nameof(UseConsole));
|
||||
settings.RegisterSimple(this, () => this.CreateConsoleWindow, "Console", nameof(CreateConsoleWindow));
|
||||
}
|
||||
}
|
||||
118
VisionBuilder.UI.Console/WinConsole.cs
Normal file
118
VisionBuilder.UI.Console/WinConsole.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace VisionBuilder.UI.Console
|
||||
{
|
||||
public static class WinConsole
|
||||
{
|
||||
public static void Initialize(bool alwaysCreateNewConsole = true, bool minimizeWindow = false)
|
||||
{
|
||||
bool consoleAttached = true;
|
||||
if (alwaysCreateNewConsole
|
||||
|| (AttachConsole(ATTACH_PARRENT) == 0
|
||||
&& Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
|
||||
{
|
||||
consoleAttached = AllocConsole() != 0;
|
||||
}
|
||||
|
||||
if (consoleAttached)
|
||||
{
|
||||
InitializeOutStream();
|
||||
InitializeInStream();
|
||||
}
|
||||
|
||||
if (minimizeWindow)
|
||||
{
|
||||
const int SW_MINIMIZE = 6;
|
||||
var handle = GetConsoleWindow();
|
||||
ShowWindow(handle, SW_MINIMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MinimizeWindow()
|
||||
{
|
||||
const int SW_MINIMIZE = 6;
|
||||
var handle = GetConsoleWindow();
|
||||
ShowWindow(handle, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetConsoleWindow();
|
||||
private static void InitializeOutStream()
|
||||
{
|
||||
var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
|
||||
if (fs != null)
|
||||
{
|
||||
var writer = new StreamWriter(fs) { AutoFlush = true };
|
||||
System.Console.SetOut(writer);
|
||||
System.Console.SetError(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeInStream()
|
||||
{
|
||||
var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
|
||||
if (fs != null)
|
||||
{
|
||||
System.Console.SetIn(new StreamReader(fs));
|
||||
}
|
||||
}
|
||||
|
||||
private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
|
||||
FileAccess dotNetFileAccess)
|
||||
{
|
||||
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
|
||||
if (!file.IsInvalid)
|
||||
{
|
||||
var fs = new FileStream(file, dotNetFileAccess);
|
||||
return fs;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Win API Functions and Constants
|
||||
[DllImport("kernel32.dll",
|
||||
EntryPoint = "AllocConsole",
|
||||
SetLastError = true,
|
||||
CharSet = CharSet.Auto,
|
||||
CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern int AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll",
|
||||
EntryPoint = "AttachConsole",
|
||||
SetLastError = true,
|
||||
CharSet = CharSet.Auto,
|
||||
CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern UInt32 AttachConsole(UInt32 dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll",
|
||||
EntryPoint = "CreateFileW",
|
||||
SetLastError = true,
|
||||
CharSet = CharSet.Auto,
|
||||
CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern IntPtr CreateFileW(
|
||||
string lpFileName,
|
||||
UInt32 dwDesiredAccess,
|
||||
UInt32 dwShareMode,
|
||||
IntPtr lpSecurityAttributes,
|
||||
UInt32 dwCreationDisposition,
|
||||
UInt32 dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile
|
||||
);
|
||||
|
||||
private const UInt32 GENERIC_WRITE = 0x40000000;
|
||||
private const UInt32 GENERIC_READ = 0x80000000;
|
||||
private const UInt32 FILE_SHARE_READ = 0x00000001;
|
||||
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
|
||||
private const UInt32 OPEN_EXISTING = 0x00000003;
|
||||
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
|
||||
private const UInt32 ERROR_ACCESS_DENIED = 5;
|
||||
|
||||
private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
15
VisionBuilder.UI.Recipes.HawkeyeRecipe/ModuleExtensions.cs
Normal file
15
VisionBuilder.UI.Recipes.HawkeyeRecipe/ModuleExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace VisionBuilder.UI.Recipes.HawkeyeRecipe;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IChildKernel UseHawkeyeRecipes(this IChildKernel self, string cameraName)
|
||||
{
|
||||
self.Bind<HawkeyeRecognitionSettings, ISettings>().ToConstant(new HawkeyeRecognitionSettings(cameraName));
|
||||
self.Bind<IRecognitionControl>().To<HawkeyeRecognitionControl>().InSingletonScope();
|
||||
return self;
|
||||
}
|
||||
}
|
||||
14
VisionBuilder.UI.Ringbuffer/ModuleExtensions.cs
Normal file
14
VisionBuilder.UI.Ringbuffer/ModuleExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Ringbuffer;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IChildKernel UseRingbuffer(this IChildKernel self, string cameraName)
|
||||
{
|
||||
self.Bind<VisionBuilderRingbufferSettings, ISettings>().ToConstant(new VisionBuilderRingbufferSettings(cameraName));
|
||||
self.RegisterModule<VisionBuilderRingbuffer>();
|
||||
return self;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using Inspectron.Fastbuffer;
|
||||
using Inspectron.Fastbuffer.Filesystems;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using Lindt.Colorballs.Duo.Utils;
|
||||
using OpenCvSharp;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace Lindt.Colorballs.Duo.Utils;
|
||||
namespace VisionBuilder.UI.Ringbuffer;
|
||||
|
||||
public class VisionBuilderRingbuffer
|
||||
public class VisionBuilderRingbuffer:IVisionBuilderModule
|
||||
{
|
||||
private readonly VisionBuilderRingbufferSettings _settings;
|
||||
private readonly IRecognitionControl _recognitionControl;
|
||||
@@ -17,10 +17,13 @@ public class VisionBuilderRingbuffer
|
||||
{
|
||||
_settings = settings;
|
||||
_recognitionControl = recognitionControl;
|
||||
|
||||
}
|
||||
public void InitializeModule()
|
||||
{
|
||||
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
|
||||
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
|
||||
}
|
||||
|
||||
private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj)
|
||||
{
|
||||
if (obj.HasError)
|
||||
@@ -124,4 +127,6 @@ public class VisionBuilderRingbuffer
|
||||
_badRingbuffers.Clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using Inspectron.Settings;
|
||||
using Inspectron.Settings.Attributes;
|
||||
using System.ComponentModel;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Lindt.Colorballs.Duo.Utils;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace Lindt.Colorballs.Duo.Utils;
|
||||
namespace VisionBuilder.UI.Ringbuffer;
|
||||
|
||||
public class VisionBuilderRingbufferSettings : ISettings
|
||||
{
|
||||
@@ -23,48 +22,25 @@ public class VisionBuilderRingbufferSettings : ISettings
|
||||
[SettingDescription(
|
||||
@"
|
||||
Specifies directory, in which MaxBad and MaxGood are applied.
|
||||
You can use the following system variables:
|
||||
|
||||
$RECIPE_NAME - name of the current recipe
|
||||
$DEFECT_NAME - name of the current defect(for bad images)
|
||||
$CAMERA_NAME - name of the camera
|
||||
|
||||
$HOUR - hour of the image
|
||||
$MINUTE - minute of the image
|
||||
$SECOND - second of the image
|
||||
$MS - milliseconds of the image
|
||||
|
||||
$DAY - day of the image
|
||||
$MONTH - month of the image
|
||||
$YEAR - year of the image
|
||||
|
||||
$SS_HOUR - hour of the session start
|
||||
$SS_MINUTE - minute of the session start
|
||||
$SS_SECOND - second of the session start
|
||||
|
||||
$SS_DAY - day of the session start
|
||||
$SS_MONTH - month of the session start
|
||||
$SS_YEAR - year of the session start
|
||||
|
||||
"
|
||||
"+ PathSettingsUtils.INSTUCTIONS
|
||||
)]
|
||||
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
|
||||
public string Root { get; set; } = @"..\Data\Ringbuffer\$SS_MONTH $SS_YEAR";
|
||||
public string Root { get; set; } = @"..\Data\Ringbuffer\$SS_MONTH_NAME $SS_YEAR";
|
||||
|
||||
|
||||
|
||||
[SettingDescriptionAttribute("Specifies directory, relative to the Root")]
|
||||
[SettingDescription("Specifies directory, relative to the Root")]
|
||||
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
|
||||
public string GoodImagesPathTemplate { get; set; } = @"\$RECIPE_NAME";
|
||||
|
||||
[SettingDescriptionAttribute("Specifies directory, relative to the Root")]
|
||||
[SettingDescription("Specifies directory, relative to the Root")]
|
||||
[SettingPreview(typeof(VisionBuilderRingbufferSettings),nameof(ImagesPathTemplatePreview))]
|
||||
public string BadImagesPathTemplate { get; set; } =
|
||||
@"\$RECIPE_NAME\$DEFECT_NAME";
|
||||
|
||||
|
||||
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
|
||||
public string FilenameTemplate { get; set; } = "$YEAR-$MONTH-$DAY $HOUR-$MINUTE-$SECOND $MS";
|
||||
public string FilenameTemplate { get; set; } = "$YEAR-$MONTH_NUM-$DAY $HOUR-$MINUTE-$SECOND $MS";
|
||||
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
|
||||
14
VisionBuilder.UI.Statistics/ModuleExtensions.cs
Normal file
14
VisionBuilder.UI.Statistics/ModuleExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IChildKernel UseStatistics(this IChildKernel self, string cameraName)
|
||||
{
|
||||
self.Bind<VisionBuilderStatisticsSettings, ISettings>().ToConstant(new VisionBuilderStatisticsSettings(cameraName));
|
||||
self.RegisterModule<VisionBuilderStatistics>();
|
||||
return self;
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,9 @@ using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics
|
||||
{
|
||||
public class VisionBuilderStatistics:IInitializable
|
||||
public class VisionBuilderStatistics:IVisionBuilderModule,IDisposable
|
||||
{
|
||||
private CsvStatistics _csvStatistics;
|
||||
private CsvStatistics? _csvStatistics;
|
||||
private readonly VisionBuilderStatisticsSettings _settings;
|
||||
private string _filePath;
|
||||
private readonly IRecognitionControl _recognitionControl;
|
||||
@@ -29,7 +29,7 @@ namespace VisionBuilder.UI.Statistics
|
||||
private const string MONTHLY_FILE_NAME = "statistics_month.csv";
|
||||
private const string EMPTY_DATE = "";
|
||||
private const string EMPTY_TIME = "";
|
||||
|
||||
public bool DoNotDeleteTempFiles { get; set; } = false;
|
||||
|
||||
public VisionBuilderStatistics(VisionBuilderStatisticsSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService)
|
||||
{
|
||||
@@ -37,18 +37,20 @@ namespace VisionBuilder.UI.Statistics
|
||||
_recognitionControl = recognitionControl;
|
||||
_loadingService = loadingService;
|
||||
|
||||
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
|
||||
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
|
||||
_recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
|
||||
|
||||
}
|
||||
|
||||
private void _recognitionControl_SessionEnded(SessionEndedEvent obj)
|
||||
{
|
||||
var endedAt = obj.SessionEnded;
|
||||
_endTime = endedAt;
|
||||
_loadingService.StartLoading("Writing statistics for " + _settings.CameraName);
|
||||
try
|
||||
{
|
||||
_csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T"), DateTime.Now.ToString("d"), DateTime.Now.ToString("T"), "", "", ""]);
|
||||
_csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T"), endedAt.ToString("d"), endedAt.ToString("T"), "", "", ""]);
|
||||
_csvStatistics.Dispose();
|
||||
_csvStatistics = null;
|
||||
|
||||
WriteFinalStatistics();
|
||||
}
|
||||
finally
|
||||
@@ -67,10 +69,10 @@ namespace VisionBuilder.UI.Statistics
|
||||
|
||||
private void _recognitionControl_SessionStarted(SessionStartedEvent obj)
|
||||
{
|
||||
_startTime = DateTime.Now;
|
||||
_startTime = obj.SessionStarted;
|
||||
_currentRecipe = obj.RecipeName;
|
||||
_fileName = "tmp_"+Guid.NewGuid().ToString()+".csv";
|
||||
_directoryPath = PathSettingsUtils.ConvertVariables(_settings.PathTemplate, DateTime.Now, DateTime.Now,
|
||||
_directoryPath = PathSettingsUtils.ConvertVariables(_settings.PathTemplate, _startTime, _startTime,
|
||||
obj.RecipeName, cameraName: _settings.CameraName);
|
||||
_filePath = Path.Combine(_directoryPath, _fileName);
|
||||
var headers = new[] { _settings.StartDateColumnName, _settings.StartTimeColumnName, _settings.EndDateColumnName, _settings.EndTimeColumnName, _settings.ErrorTimeColumnName, _settings.ProductColumnName, _settings.ErrorColumnName };
|
||||
@@ -84,6 +86,8 @@ namespace VisionBuilder.UI.Statistics
|
||||
}
|
||||
|
||||
ConcurrentDictionary<string, int> _defects = new();
|
||||
private DateTime _endTime;
|
||||
|
||||
private void RecordBadImage(string defectName)
|
||||
{
|
||||
|
||||
@@ -96,50 +100,60 @@ namespace VisionBuilder.UI.Statistics
|
||||
_defects.AddOrUpdate(defectName, 1, (key, oldValue) => oldValue + 1);
|
||||
}
|
||||
|
||||
private void WriteMonthStatistics(string filename)
|
||||
private void WriteMonthStatistics()
|
||||
{
|
||||
// read second line to get the start time
|
||||
var lines = File.ReadAllLines(filename);
|
||||
var lastLine = lines[^1];
|
||||
if (lines.Length < 2) return; // no data to write
|
||||
var startDate = DateTime.Parse(lastLine.Split(',')[0]);
|
||||
var startTime = DateTime.Parse(lastLine.Split(',')[1]);
|
||||
var endDate = DateTime.Parse(lastLine.Split(',')[2]);
|
||||
var endTime = DateTime.Parse(lastLine.Split(',')[3]);
|
||||
|
||||
// all must be non-empty
|
||||
if (startDate == DateTime.MinValue || startTime == DateTime.MinValue || endDate == DateTime.MinValue || endTime == DateTime.MinValue)
|
||||
var startDate = _startTime.Date;
|
||||
var startTime = _startTime;
|
||||
var endDate = _endTime.Date;
|
||||
var endTime = _endTime;
|
||||
|
||||
var monthFilePath = Path.Combine(_directoryPath, MONTHLY_FILE_NAME);
|
||||
|
||||
var monthStatistics = new CsvStatistics(monthFilePath, [
|
||||
"Date start","Time start","Date end", "Time end" ,"Product","Error type","Error count"
|
||||
]);
|
||||
|
||||
foreach (var defect in _defects)
|
||||
{
|
||||
Log.Error("Invalid date or time in statistics file: " + filename);
|
||||
throw new InvalidOperationException("Invalid date or time in statistics file: " + filename);
|
||||
monthStatistics.WriteLine(
|
||||
[startDate.ToString("d"), startTime.ToString("T"), endDate.ToString("d"), endTime.ToString("T"), _currentRecipe, defect.Key, defect.Value.ToString()]
|
||||
);
|
||||
}
|
||||
|
||||
monthStatistics.Dispose();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void WriteFinalStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmpFiles = Directory.GetFiles(_directoryPath, "tmp_*");
|
||||
// order by creation time
|
||||
Array.Sort(tmpFiles, (x, y) => File.GetCreationTime(x).CompareTo(File.GetCreationTime(y)));
|
||||
foreach (var file in tmpFiles)
|
||||
{
|
||||
var finalFileName = Path.Combine(_directoryPath, BY_EVENTS_FILE_NAME);
|
||||
var lines = File.ReadAllLines(file);
|
||||
File.AppendAllLines(finalFileName, lines.Skip(1)); // skip header
|
||||
WriteMonthStatistics(file);
|
||||
File.Delete(file);
|
||||
}
|
||||
Log.Debug("Writing month statistics");
|
||||
WriteMonthStatistics();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Error writing statistics for " + _settings.CameraName+ e);
|
||||
Log.Error("Error writing statistics for " + _settings.CameraName+ "\n"+ e);
|
||||
}
|
||||
_csvStatistics?.Dispose();
|
||||
_csvStatistics = null;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
public void InitializeModule()
|
||||
{
|
||||
WriteFinalStatistics();
|
||||
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
|
||||
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
|
||||
_recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_csvStatistics?.Dispose();
|
||||
_recognitionControl.SessionStarted -= _recognitionControl_SessionStarted;
|
||||
_recognitionControl.ImageProcessed -= _recognitionControl_ImageProcessed;
|
||||
_recognitionControl.SessionEnded -= _recognitionControl_SessionEnded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Inspectron.Settings;
|
||||
using Inspectron.Settings.Attributes;
|
||||
using Lindt.Colorballs.Duo.Utils;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace VisionBuilder.UI.Statistics;
|
||||
@@ -16,31 +17,10 @@ public class VisionBuilderStatisticsSettings: ISettings
|
||||
[SettingDescription(
|
||||
@"
|
||||
Specifies directory, in which MaxBad and MaxGood are applied.
|
||||
You can use the following system variables:
|
||||
|
||||
$RECIPE_NAME - name of the current recipe
|
||||
$DEFECT_NAME - name of the current defect(for bad images)
|
||||
$CAMERA_NAME - name of the camera
|
||||
|
||||
$HOUR - hour of the image
|
||||
$MINUTE - minute of the image
|
||||
$SECOND - second of the image
|
||||
$MS - milliseconds of the image
|
||||
|
||||
$DAY - day of the image
|
||||
$MONTH - month of the image
|
||||
$YEAR - year of the image
|
||||
|
||||
$SS_HOUR - hour of the session start
|
||||
$SS_MINUTE - minute of the session start
|
||||
$SS_SECOND - second of the session start
|
||||
|
||||
$SS_DAY - day of the session start
|
||||
$SS_MONTH - month of the session start
|
||||
$SS_YEAR - year of the session start
|
||||
"
|
||||
"+PathSettingsUtils.INSTUCTIONS
|
||||
)]
|
||||
public string PathTemplate { get; set; }= @$"..\Data\Statistics\$SS_MONTH $SS_YEAR\$RECIPE_NAME\";
|
||||
[SettingPreview(typeof(VisionBuilderStatisticsSettings), nameof(PathTemplatePreview))]
|
||||
public string PathTemplate { get; set; }= @$"..\Data\Statistics\$SS_MONTH_NUM $SS_YEAR\$RECIPE_NAME\";
|
||||
|
||||
public string StartDateColumnName { get; set; } = "Start Date";
|
||||
public string StartTimeColumnName { get; set; } = "Start time";
|
||||
@@ -61,4 +41,10 @@ $SS_YEAR - year of the session start
|
||||
settings.RegisterSimple(this,()=>this.ProductColumnName,CameraName+"/"+"Statistics",nameof(ProductColumnName));
|
||||
settings.RegisterSimple(this,()=>this.ErrorColumnName,CameraName+"/"+"Statistics",nameof(ErrorColumnName));
|
||||
}
|
||||
|
||||
public static string PathTemplatePreview(object template)
|
||||
{
|
||||
return PathSettingsUtils.ConvertVariables((string)template, DateTime.Now, DateTime.Now, "my recipe",
|
||||
"my defect", "my camera");
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Statistics\VisionBuilder.UI.Statistics.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
471
VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs
Normal file
471
VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs
Normal file
@@ -0,0 +1,471 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.Commands;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
using VisionBuilder.UI.Common.ViewModel.Classes;
|
||||
using VisionBuilder.UI.Statistics;
|
||||
|
||||
namespace VisionBuilder.UI.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class VisionBuilderStatisticsTests
|
||||
{
|
||||
private string _tempDirectory;
|
||||
private VisionBuilderStatisticsSettings _settings;
|
||||
private TestRecognitionControl _recognitionControl;
|
||||
private TestLoadingService _loadingService;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), "VisionBuilderStatisticsTests", Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
|
||||
_settings = new VisionBuilderStatisticsSettings("TestCamera")
|
||||
{
|
||||
PathTemplate = _tempDirectory,
|
||||
StartDateColumnName = "Start Date",
|
||||
StartTimeColumnName = "Start time",
|
||||
EndDateColumnName = "End Date",
|
||||
EndTimeColumnName = "End time",
|
||||
ErrorTimeColumnName = "Error time",
|
||||
ProductColumnName = "Product",
|
||||
ErrorColumnName = "Error"
|
||||
};
|
||||
|
||||
_recognitionControl = new TestRecognitionControl();
|
||||
_loadingService = new TestLoadingService();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
if (Directory.Exists(_tempDirectory))
|
||||
{
|
||||
Directory.Delete(_tempDirectory, true);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SessionStarted_CreatesTemporaryFile_WithCorrectHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
Assert.AreEqual(1, tmpFiles.Length, "Should create exactly one temporary file");
|
||||
|
||||
var lines = File.ReadAllLines(tmpFiles[0]);
|
||||
Assert.IsTrue(lines.Length >= 2, "Should have header and at least one data line");
|
||||
|
||||
var expectedHeaders = new[] { "Start Date", "Start time", "End Date", "End time", "Error time", "Product", "Error" };
|
||||
var actualHeaders = lines[0].Split(',');
|
||||
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Headers should match settings");
|
||||
|
||||
var firstDataLine = lines[1].Split(',');
|
||||
Assert.IsTrue(DateTime.TryParse(firstDataLine[0], out _), "First column should be a valid date");
|
||||
Assert.IsTrue(DateTime.TryParse(firstDataLine[1], out _), "Second column should be a valid time");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ImageProcessed_WithError_RecordsDefectInFile()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
var imageProcessedEvent = new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "TestDefect" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
var lines = File.ReadAllLines(tmpFiles[0]);
|
||||
|
||||
Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and error record");
|
||||
|
||||
var errorLine = lines[2].Split(',');
|
||||
Assert.AreEqual("TestDefect", errorLine[6], "Error name should be recorded in correct column");
|
||||
Assert.AreEqual("TestRecipe", errorLine[5], "Recipe name should be recorded in correct column");
|
||||
Assert.IsTrue(DateTime.TryParse(errorLine[4], out _), "Error time should be valid");
|
||||
|
||||
// Verify defect counting using reflection
|
||||
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
|
||||
Assert.AreEqual(1, defects["TestDefect"], "Defect count should be incremented");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ImageProcessed_WithoutError_DoesNotRecordDefect()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
var imageProcessedEvent = new ImageProcessedEvent
|
||||
{
|
||||
HasError = false,
|
||||
ErrorNames = new List<string>(),
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
var lines = File.ReadAllLines(tmpFiles[0]);
|
||||
|
||||
Assert.AreEqual(2, lines.Length, "Should only have header and session start line");
|
||||
|
||||
// Verify defect counting using reflection
|
||||
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
|
||||
Assert.AreEqual(0, defects.Count, "No defects should be recorded");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SessionEnded_WritesEndTimeAndDisposesCsv()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService)
|
||||
{
|
||||
DoNotDeleteTempFiles = true
|
||||
};
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
var sessionEndEvent = new SessionEndedEvent
|
||||
{
|
||||
SessionEnded = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
var lines = File.ReadAllLines(tmpFiles[0]);
|
||||
|
||||
Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and session end");
|
||||
|
||||
var endLine = lines[2].Split(',');
|
||||
Assert.IsTrue(DateTime.TryParse(endLine[2], out _), "End date should be valid");
|
||||
Assert.IsTrue(DateTime.TryParse(endLine[3], out _), "End time should be valid");
|
||||
|
||||
Assert.IsTrue(_loadingService.StartLoadingCalled, "Should start loading during session end");
|
||||
Assert.IsTrue(_loadingService.StopLoadingCalled, "Should stop loading after session end");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteFinalStatistics_ConsolidatesTemporaryFiles()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
var imageProcessedEvent = new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "TestDefect" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = DateTime.Now
|
||||
};
|
||||
|
||||
var sessionEndEvent = new SessionEndedEvent
|
||||
{
|
||||
SessionEnded = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent);
|
||||
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
|
||||
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
Assert.AreEqual(0, tmpFiles.Length, "Temporary files should be deleted after consolidation");
|
||||
|
||||
var finalFile = Path.Combine(_tempDirectory, "statistics_by_events.csv");
|
||||
Assert.IsTrue(File.Exists(finalFile), "Final statistics file should be created");
|
||||
|
||||
var finalLines = File.ReadAllLines(finalFile);
|
||||
Assert.IsTrue(finalLines.Length >= 2, "Final file should contain session data");
|
||||
|
||||
// Verify the defect was recorded in the final file
|
||||
var defectLine = finalLines.FirstOrDefault(l => l.Contains("TestDefect"));
|
||||
Assert.IsNotNull(defectLine, "Defect should be recorded in final file");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleDefects_CountedCorrectly()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
|
||||
// Process multiple images with same defect
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
_recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "DefectA" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
// Process images with different defect
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
_recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "DefectB" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = DateTime.Now
|
||||
});
|
||||
}
|
||||
statistics.Dispose(); // Ensure file is written and closed
|
||||
// Assert
|
||||
var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var defects = (ConcurrentDictionary<string, int>)defectsField.GetValue(statistics);
|
||||
|
||||
Assert.AreEqual(3, defects["DefectA"], "DefectA should be counted correctly");
|
||||
Assert.AreEqual(2, defects["DefectB"], "DefectB should be counted correctly");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PathTemplate_WithVariables_ReplacedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
|
||||
{
|
||||
PathTemplate = Path.Combine(_tempDirectory, "$RECIPE_NAME", "$SS_YEAR", "$SS_MONTH")
|
||||
};
|
||||
|
||||
var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
|
||||
customStatistics.InitializeModule();
|
||||
var testDate = new DateTime(2023, 5, 15, 10, 30, 0);
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "MyRecipe",
|
||||
SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
customStatistics.Dispose(); // Ensure file is written and closed
|
||||
// Assert
|
||||
var expectedPath = Path.Combine(_tempDirectory, "MyRecipe", testDate.ToString("yyyy"), testDate.ToString("MMMM"));
|
||||
Assert.IsTrue(Directory.Exists(expectedPath), "Directory should be created with expanded variables");
|
||||
|
||||
var tmpFiles = Directory.GetFiles(expectedPath, "tmp_*.csv");
|
||||
Assert.AreEqual(1, tmpFiles.Length, "Temporary file should be created in correct directory");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CustomColumnNames_UsedInHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
|
||||
{
|
||||
PathTemplate = _tempDirectory,
|
||||
StartDateColumnName = "Custom Start Date",
|
||||
StartTimeColumnName = "Custom Start Time",
|
||||
EndDateColumnName = "Custom End Date",
|
||||
EndTimeColumnName = "Custom End Time",
|
||||
ErrorTimeColumnName = "Custom Error Time",
|
||||
ProductColumnName = "Custom Product",
|
||||
ErrorColumnName = "Custom Error"
|
||||
};
|
||||
|
||||
var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
|
||||
customStatistics.InitializeModule();
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = DateTime.Now
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
|
||||
customStatistics.Dispose();
|
||||
|
||||
// Assert
|
||||
var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
|
||||
var lines = File.ReadAllLines(tmpFiles[0]);
|
||||
|
||||
var expectedHeaders = new[] { "Custom Start Date", "Custom Start Time", "Custom End Date", "Custom End Time", "Custom Error Time", "Custom Product", "Custom Error" };
|
||||
var actualHeaders = lines[0].Split(',');
|
||||
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Custom column names should be used in headers");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WriteMonthStatistics_CreatesMonthlyFileWithDefectCounts()
|
||||
{
|
||||
var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
|
||||
statistics.InitializeModule();
|
||||
// Arrange
|
||||
var sessionStartEvent = new SessionStartedEvent
|
||||
{
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
|
||||
};
|
||||
|
||||
var imageProcessedEvent1 = new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "DefectA" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
|
||||
};
|
||||
|
||||
var imageProcessedEvent2 = new ImageProcessedEvent
|
||||
{
|
||||
HasError = true,
|
||||
ErrorNames = new List<string> { "DefectB" },
|
||||
RecipeName = "TestRecipe",
|
||||
SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
|
||||
};
|
||||
|
||||
var sessionEndEvent = new SessionEndedEvent
|
||||
{
|
||||
SessionEnded = new DateTime(2023, 5, 15, 11, 45, 0)
|
||||
};
|
||||
|
||||
// Act
|
||||
_recognitionControl.TriggerSessionStarted(sessionStartEvent);
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent1);
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent1); // Same defect again
|
||||
_recognitionControl.TriggerImageProcessed(imageProcessedEvent2);
|
||||
_recognitionControl.TriggerSessionEnded(sessionEndEvent);
|
||||
statistics.Dispose();
|
||||
|
||||
// Assert
|
||||
var monthlyFile = Path.Combine(_tempDirectory, "statistics_month.csv");
|
||||
Assert.IsTrue(File.Exists(monthlyFile), "Monthly statistics file should be created");
|
||||
|
||||
var monthlyLines = File.ReadAllLines(monthlyFile);
|
||||
Assert.IsTrue(monthlyLines.Length >= 3, "Should have header and defect records");
|
||||
|
||||
var expectedHeaders = new[] { "Date start", "Time start", "Date end", "Time end", "Product", "Error type", "Error count" };
|
||||
var actualHeaders = monthlyLines[0].Split(',');
|
||||
CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Monthly file should have correct headers");
|
||||
|
||||
// Verify defect counts
|
||||
var defectALine = monthlyLines.FirstOrDefault(l => l.Contains("DefectA"));
|
||||
var defectBLine = monthlyLines.FirstOrDefault(l => l.Contains("DefectB"));
|
||||
|
||||
Assert.IsNotNull(defectALine, "DefectA should be recorded in monthly file");
|
||||
Assert.IsNotNull(defectBLine, "DefectB should be recorded in monthly file");
|
||||
|
||||
var defectAData = defectALine.Split(',');
|
||||
var defectBData = defectBLine.Split(',');
|
||||
|
||||
Assert.AreEqual("2", defectAData[6], "DefectA count should be 2");
|
||||
Assert.AreEqual("1", defectBData[6], "DefectB count should be 1");
|
||||
Assert.AreEqual("TestRecipe", defectAData[4], "Product name should be correct");
|
||||
Assert.AreEqual("TestRecipe", defectBData[4], "Product name should be correct");
|
||||
|
||||
// Verify dates and times
|
||||
Assert.IsTrue(DateTime.TryParse(defectAData[0], out var startDate), "Start date should be valid");
|
||||
Assert.IsTrue(DateTime.TryParse(defectAData[1], out var startTime), "Start time should be valid");
|
||||
Assert.IsTrue(DateTime.TryParse(defectAData[2], out var endDate), "End date should be valid");
|
||||
Assert.IsTrue(DateTime.TryParse(defectAData[3], out var endTime), "End time should be valid");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Test implementation of IRecognitionControl using reflection approach
|
||||
public class TestRecognitionControl : IRecognitionControl
|
||||
{
|
||||
public event Action<ImageProcessedEvent> ImageProcessed = delegate { };
|
||||
public event Action<SessionStartedEvent> SessionStarted = delegate { };
|
||||
public event Action<SessionEndedEvent> SessionEnded = delegate { };
|
||||
|
||||
public List<RecipeData> GetRecipesData() => new List<RecipeData>();
|
||||
public void SetRecipe(RecipeData recipe) { }
|
||||
public void Start() { }
|
||||
public void Stop() { }
|
||||
|
||||
public void TriggerImageProcessed(ImageProcessedEvent eventArgs) => ImageProcessed(eventArgs);
|
||||
public void TriggerSessionStarted(SessionStartedEvent eventArgs) => SessionStarted(eventArgs);
|
||||
public void TriggerSessionEnded(SessionEndedEvent eventArgs) => SessionEnded(eventArgs);
|
||||
}
|
||||
|
||||
// Test implementation of ILoadingService
|
||||
public class TestLoadingService : ILoadingService
|
||||
{
|
||||
public bool StartLoadingCalled { get; private set; }
|
||||
public bool StopLoadingCalled { get; private set; }
|
||||
public string LastLoadingMessage { get; private set; }
|
||||
|
||||
public void StartLoading(string message)
|
||||
{
|
||||
StartLoadingCalled = true;
|
||||
LastLoadingMessage = message;
|
||||
}
|
||||
|
||||
public void StopLoading(string message)
|
||||
{
|
||||
StopLoadingCalled = true;
|
||||
LastLoadingMessage = message;
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,19 @@ using Inspectron.Settings;
|
||||
using Inspectron.Settings.Windows.Configuration;
|
||||
using Lindt.Colorballs.Duo.Utils;
|
||||
using Ninject;
|
||||
using Ninject.Extensions.ChildKernel;
|
||||
using OpenCvSharp;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
using VisionBuilder.UI.Console;
|
||||
using VisionBuilder.UI.Recipes.HawkeyeRecipe;
|
||||
using VisionBuilder.UI.Ringbuffer;
|
||||
using VisionBuilder.UI.Statistics;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Test
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private const string CAMERA1 = "Camera 1";
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
@@ -29,58 +34,42 @@ namespace VisionBuilder.UI.Windows.Test
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
|
||||
|
||||
StandardKernel kernelCamera1 = new StandardKernel();
|
||||
|
||||
|
||||
// SETTINGS //
|
||||
InspectronSettings settings = new InspectronSettings("..\\Config\\AppSettings.xml");
|
||||
kernelCamera1.Bind<InspectronSettings>().ToConstant(settings);
|
||||
kernelCamera1.Bind<UIConfiguration,ISettings>().ToConstant(new UIConfiguration());
|
||||
kernelCamera1.Bind<CameraSettings, ISettings>().ToConstant(new CameraSettings("Camera 1"));
|
||||
kernelCamera1.Bind<EmulationSettings, ISettings>().ToConstant(new EmulationSettings("Camera 1"));
|
||||
kernelCamera1.Bind<HawkeyeRecognitionSettings, ISettings>().ToConstant(new HawkeyeRecognitionSettings("Camera 1"));
|
||||
kernelCamera1.Bind<VisionBuilderRingbufferSettings, ISettings>().ToConstant(new VisionBuilderRingbufferSettings("Camera 1"));
|
||||
|
||||
var mainKernel = Common.VisionBuilder.CreateMainKernel(settings);
|
||||
|
||||
|
||||
|
||||
// CAMERA 1 //
|
||||
ChildKernel kernelCamera1 = new ChildKernel(mainKernel);
|
||||
mainKernel.UseWindowsServices();
|
||||
|
||||
// MODULES //
|
||||
kernelCamera1
|
||||
.RegisterCamera(CAMERA1)
|
||||
.UseStatistics(CAMERA1)
|
||||
.UseRingbuffer(CAMERA1)
|
||||
.UseHawkeyeRecipes(CAMERA1)
|
||||
.UseConsole();
|
||||
|
||||
|
||||
// LOAD SETTINGS //
|
||||
kernelCamera1.RegisterSettings();
|
||||
settings.LoadSettings();
|
||||
|
||||
// Create camera and bind it
|
||||
// Must be done after settings are loaded
|
||||
kernelCamera1.UseCamera();
|
||||
|
||||
// --- //
|
||||
|
||||
// SOURCES //
|
||||
var cameraSettings = kernelCamera1.Get<CameraSettings>();
|
||||
switch (cameraSettings.Camera1Source)
|
||||
{
|
||||
case CameraSettings.EImageSource.Emulation:
|
||||
kernelCamera1.Bind<IImageSource>().To<EmulationCameraImageSource>().InSingletonScope();
|
||||
break;
|
||||
case CameraSettings.EImageSource.Hawkeye:
|
||||
break;
|
||||
case CameraSettings.EImageSource.IDS:
|
||||
break;
|
||||
case CameraSettings.EImageSource.Basler:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
// --- //
|
||||
|
||||
// SERVICES //
|
||||
kernelCamera1.Bind<ISettingsService>().To<SettingsService>().InSingletonScope();
|
||||
kernelCamera1.Bind<IRecognitionControl>().To<HawkeyeRecognitionControl>().InSingletonScope();
|
||||
kernelCamera1.Bind<VisionBuilderRingbuffer>().ToSelf().InSingletonScope();
|
||||
kernelCamera1.Bind<ILoadingService>().To<LoadingService>().InSingletonScope();
|
||||
kernelCamera1.InitializeModules();
|
||||
|
||||
kernelCamera1.Get<VisionBuilderRingbuffer>(); // initialize the ringbuffer
|
||||
var mainWindowVm = mainKernel.Get<MainWindowVM>();
|
||||
mainWindowVm.SingleCameraVms = [
|
||||
kernelCamera1.Get<SingleCameraVM>()
|
||||
];
|
||||
|
||||
var mainWindowVm = new MainWindowVM(kernelCamera1.Get<ISettingsService>())
|
||||
{
|
||||
SingleCameraVms = new ObservableCollection<SingleCameraVM>()
|
||||
{
|
||||
new SingleCameraVM(kernelCamera1.Get<CameraSettings>(),kernelCamera1.Get<UIConfiguration>(),new RecipeSelectionDialogService(),kernelCamera1.Get<IRecognitionControl>(), new ImagePreviewService(), new NoLearning())
|
||||
}
|
||||
};
|
||||
|
||||
settings.LoadSettings();
|
||||
Application.Run(new Form1(mainWindowVm));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.6.0.20220608" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -17,9 +18,12 @@
|
||||
<ProjectReference Include="..\framework\Inspectron.Settings\Inspectron.Settings.csproj" />
|
||||
<ProjectReference Include="..\framework\MaterialSkin.Core\MaterialSkin.Core.csproj" />
|
||||
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.Emulation\Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Camera\VisionBuilder.UI.Camera.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Console\VisionBuilder.UI.Console.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Recipes.HawkeyeRecipe\VisionBuilder.UI.Recipes.HawkeyeRecipe.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Ringbuffer\VisionBuilder.UI.Ringbuffer.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Statistics\VisionBuilder.UI.Statistics.csproj" />
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Windows\VisionBuilder.UI.Windows.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
120
VisionBuilder.UI.Windows/Components/PreviewWindow.resx
Normal file
120
VisionBuilder.UI.Windows/Components/PreviewWindow.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -31,7 +31,7 @@ namespace VisionBuilder.UI.Windows.Components
|
||||
btnStop.Command=_singleCameraVm.StopCommand;
|
||||
btnSelectRecipe.Command=_singleCameraVm.SelectRecipeCommand;
|
||||
|
||||
lblCameraName.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CameraName), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
lblCameraName.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CameraLabel), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
btnSelectRecipe.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CurrentRecipeName), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
|
||||
}
|
||||
|
||||
18
VisionBuilder.UI.Windows/ModuleExtensions.cs
Normal file
18
VisionBuilder.UI.Windows/ModuleExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Ninject;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
using VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
namespace VisionBuilder.UI.Windows;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IKernel UseWindowsServices(this IKernel self)
|
||||
{
|
||||
self.Bind<ISettingsService>().To<WindowsSettingsService>().InSingletonScope();
|
||||
self.Bind<ILoadingService>().To<WindowsLoadingService>().InSingletonScope();
|
||||
self.Bind<IRecipeSelectionDialogService>().To<WindowsRecipeSelectionDialogService>().InSingletonScope();
|
||||
self.Bind<IImagePreviewService>().To<WindowsImagePreviewService>().InSingletonScope();
|
||||
return self;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using MaterialSkin.Controls;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
public class LoadingService: ILoadingService
|
||||
{
|
||||
public void StartLoading(string title)
|
||||
{
|
||||
MaterialLoader.Instance.StartLoading(title);
|
||||
}
|
||||
|
||||
public void StopLoading(string title)
|
||||
{
|
||||
MaterialLoader.Instance.FinishLoading(title);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
public class ImagePreviewService: IImagePreviewService
|
||||
public class WindowsImagePreviewService: IImagePreviewService
|
||||
{
|
||||
|
||||
public ImagePreviewService()
|
||||
public WindowsImagePreviewService()
|
||||
{
|
||||
|
||||
}
|
||||
28
VisionBuilder.UI.Windows/Services/WindowsLoadingService.cs
Normal file
28
VisionBuilder.UI.Windows/Services/WindowsLoadingService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using MaterialSkin.Controls;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
public class WindowsLoadingService: ILoadingService
|
||||
{
|
||||
public void StartLoading(string title)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
MaterialLoader.Instance.StartLoading(title);
|
||||
Application.DoEvents(); // Ensure the UI updates immediately
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void StopLoading(string title)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
MaterialLoader.Instance.FinishLoading(title);
|
||||
Application.DoEvents(); // Ensure the UI updates immediately
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using VisionBuilder.UI.Windows.Dialogs;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
public class RecipeSelectionDialogService: IRecipeSelectionDialogService
|
||||
public class WindowsRecipeSelectionDialogService: IRecipeSelectionDialogService
|
||||
{
|
||||
public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm)
|
||||
{
|
||||
@@ -5,12 +5,12 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||
|
||||
namespace VisionBuilder.UI.Windows.Settings;
|
||||
|
||||
public class SettingsService: ISettingsService
|
||||
public class WindowsSettingsService: ISettingsService
|
||||
{
|
||||
private readonly UIConfiguration _uiConfiguration;
|
||||
private readonly InspectronSettings _settings;
|
||||
|
||||
public SettingsService(UIConfiguration uiConfiguration, InspectronSettings settings)
|
||||
public WindowsSettingsService(UIConfiguration uiConfiguration, InspectronSettings settings)
|
||||
{
|
||||
_uiConfiguration = uiConfiguration ?? throw new ArgumentNullException(nameof(uiConfiguration));
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
@@ -51,6 +51,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.Fastbuffer", "fr
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{C828783C-1CE1-4245-8731-4AE18A28F590}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Console", "VisionBuilder.UI.Console\VisionBuilder.UI.Console.csproj", "{4B1817EF-A3D1-48CC-BE22-4138740E450F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Camera", "VisionBuilder.UI.Camera\VisionBuilder.UI.Camera.csproj", "{FBDCC120-B6B3-4A28-8845-DF1690F619C1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.IOCommander", "VisionBuilder.UI.IOCommander\VisionBuilder.UI.IOCommander.csproj", "{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -137,6 +143,18 @@ Global
|
||||
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -155,6 +173,9 @@ Global
|
||||
{51C2BC65-5E5D-E72A-988D-215E192DE889} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||
{1F9BB3BD-65C3-4B96-A880-4908CF6D9754} = {C828783C-1CE1-4245-8731-4AE18A28F590}
|
||||
{0A23E56A-DF4C-5174-18DF-18A771957AF4} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||
{4B1817EF-A3D1-48CC-BE22-4138740E450F} = {C828783C-1CE1-4245-8731-4AE18A28F590}
|
||||
{FBDCC120-B6B3-4A28-8845-DF1690F619C1} = {C828783C-1CE1-4245-8731-4AE18A28F590}
|
||||
{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9} = {C828783C-1CE1-4245-8731-4AE18A28F590}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}
|
||||
|
||||
@@ -28,7 +28,8 @@ public class DefaultControlFactory : IControlFactory
|
||||
getPreview = value => (string)method.Invoke(null, new[] { value });
|
||||
previewLabel = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
AutoSize = false,
|
||||
Size = new System.Drawing.Size(600, 30),
|
||||
Padding = new Padding(15, 0, 0, 0),
|
||||
Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Italic),
|
||||
Text = getPreview(initialValue)
|
||||
@@ -95,7 +96,7 @@ public class DefaultControlFactory : IControlFactory
|
||||
private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback,
|
||||
string settingDescription, Label previewLabel, Func<object, string> getPreview)
|
||||
{
|
||||
var textBox = new TextBox { Text = initialValue as string, Width = 400 };
|
||||
var textBox = new TextBox { Text = initialValue as string, Width = 600 };
|
||||
var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
|
||||
|
||||
textBox.TextChanged += (s, e) =>
|
||||
|
||||
@@ -33,6 +33,7 @@ public partial class OptionsWindow
|
||||
flowLayoutPanelSettings.FlowDirection = FlowDirection.TopDown;
|
||||
flowLayoutPanelSettings.Location = new Point(220, 12);
|
||||
flowLayoutPanelSettings.Name = "flowLayoutPanelSettings";
|
||||
flowLayoutPanelSettings.Padding = new Padding(0, 0, 0, 15);
|
||||
flowLayoutPanelSettings.Size = new Size(852, 582);
|
||||
flowLayoutPanelSettings.TabIndex = 1;
|
||||
flowLayoutPanelSettings.WrapContents = false;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class CsvStatistics
|
||||
{
|
||||
_csv.Dispose();
|
||||
_stream.Dispose();
|
||||
_fileStream.Close();
|
||||
_fileStream.Dispose();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
public class StatisticsRecord
|
||||
{
|
||||
public DateTime Time { get; set; }
|
||||
public string[] Values { get; set; }
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
ControlBox = false;
|
||||
Name = "MaterialLoader";
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
TopMost = true;
|
||||
ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user