ringbuffer and statistics

This commit is contained in:
meelstorm
2025-07-18 10:13:46 +02:00
parent d3cb790bd9
commit 40d74d6da6
41 changed files with 1188 additions and 180 deletions

View File

@@ -1,11 +1,12 @@
using OpenCvSharp; using OpenCvSharp;
using System.Drawing; using System.Drawing;
using Ninject; using Ninject;
using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.RecipeProcessing; using VisionBuilder.UI.Common.RecipeProcessing;
namespace Hawkeye.VisionBuilder.UI.Sources.Emulation namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
{ {
public class EmulationCameraImageSource:IImageSource, IInitializable public class EmulationCameraImageSource:IImageSource, IVisionBuilderModule
{ {
private readonly EmulationSettings _settings; private readonly EmulationSettings _settings;
private readonly string _name; private readonly string _name;
@@ -91,7 +92,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
private int _index = 0; private int _index = 0;
public void Initialize() public void InitializeModule()
{ {
if(string.IsNullOrEmpty(_settings.EmulationPath)) return; if(string.IsNullOrEmpty(_settings.EmulationPath)) return;
SetFolderAbsolute(_settings.EmulationPath); SetFolderAbsolute(_settings.EmulationPath);

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.3.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" /> <ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
</ItemGroup> </ItemGroup>

View 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;
}
}

View 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>

View File

@@ -20,12 +20,12 @@ public class CameraSettings : ISettings
} }
public EImageSource Camera1Source { get; set; } public EImageSource CameraSource { get; set; }
public string Camera1Label { get; set; } public string CameraLabel { get; set; }
public void RegisterSettings(InspectronSettings settings) public void RegisterSettings(InspectronSettings settings)
{ {
settings.RegisterSimple(this, () => this.Camera1Source, CameraName, "Image source"); settings.RegisterSimple(this, () => this.CameraSource, CameraName, "Image source");
settings.RegisterSimple(this, () => this.Camera1Label, CameraName, "Label"); settings.RegisterSimple(this, () => this.CameraLabel, CameraName, "Label");
} }
} }

View File

@@ -1,5 +1,6 @@
using Inspectron.Settings; using Inspectron.Settings;
using Ninject; using Ninject;
using Ninject.Extensions.ChildKernel;
namespace VisionBuilder.UI.Common; namespace VisionBuilder.UI.Common;
@@ -14,6 +15,29 @@ public static class Extensions
{ {
settingsInstance.RegisterSettings(self.Get<InspectronSettings>()); 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();
}
}
}
} }

View File

@@ -0,0 +1,6 @@
namespace VisionBuilder.UI.Common;
public interface IVisionBuilderModule
{
public void InitializeModule();
}

View File

@@ -1,7 +1,32 @@
namespace VisionBuilder.UI.Common; namespace Lindt.Colorballs.Duo.Utils;
public static class PathSettingsUtils 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( public static string ConvertVariables(
string text, string text,
DateTime imageTime, DateTime imageTime,
@@ -25,7 +50,8 @@ public static class PathSettingsUtils
["$MS"] = imageTime.Millisecond.ToString("D3"), ["$MS"] = imageTime.Millisecond.ToString("D3"),
["$DAY"] = imageTime.Day.ToString("D2"), ["$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(), ["$YEAR"] = imageTime.Year.ToString(),
["$SS_HOUR"] = sessionTime.Hour.ToString("D2"), ["$SS_HOUR"] = sessionTime.Hour.ToString("D2"),
@@ -33,7 +59,8 @@ public static class PathSettingsUtils
["$SS_SECOND"] = sessionTime.Second.ToString("D2"), ["$SS_SECOND"] = sessionTime.Second.ToString("D2"),
["$SS_DAY"] = sessionTime.Day.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() ["$SS_YEAR"] = sessionTime.Year.ToString()
}; };

View File

@@ -30,7 +30,7 @@ namespace VisionBuilder.UI.Common
[NotifyCanExecuteChangedFor(nameof(StopCommand))] [NotifyCanExecuteChangedFor(nameof(StopCommand))]
private bool _isRunning; private bool _isRunning;
[ObservableProperty] [ObservableProperty]
private string _cameraName; private string _cameraLabel;
public bool CanStop => IsRunning; public bool CanStop => IsRunning;
@@ -56,7 +56,7 @@ namespace VisionBuilder.UI.Common
StatisticsVm = new StatisticsVM(); StatisticsVm = new StatisticsVM();
PreviewVm = new PreviewVM(); PreviewVm = new PreviewVM();
CameraName = cameraSettings.CameraName; CameraLabel = cameraSettings.CameraLabel;
recognitionControl.ImageProcessed += Handle; recognitionControl.ImageProcessed += Handle;

View File

@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Ninject" Version="3.3.6" /> <PackageReference Include="Ninject" Version="3.3.6" />
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
<PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" /> <PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" />
</ItemGroup> </ItemGroup>

View 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;
}
}

View 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;
}
}

View 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>

View 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");
}
}
}
}

View 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));
}
}

View 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
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -1,14 +1,14 @@
using Inspectron.Fastbuffer; using Inspectron.Fastbuffer;
using Inspectron.Fastbuffer.Filesystems; using Inspectron.Fastbuffer.Filesystems;
using System.Drawing; using Lindt.Colorballs.Duo.Utils;
using System.Drawing.Imaging;
using OpenCvSharp; using OpenCvSharp;
using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.Commands; using VisionBuilder.UI.Common.Commands;
using VisionBuilder.UI.Common.RecipeProcessing; 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 VisionBuilderRingbufferSettings _settings;
private readonly IRecognitionControl _recognitionControl; private readonly IRecognitionControl _recognitionControl;
@@ -17,10 +17,13 @@ public class VisionBuilderRingbuffer
{ {
_settings = settings; _settings = settings;
_recognitionControl = recognitionControl; _recognitionControl = recognitionControl;
}
public void InitializeModule()
{
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted; _recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed; _recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
} }
private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj) private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj)
{ {
if (obj.HasError) if (obj.HasError)
@@ -124,4 +127,6 @@ public class VisionBuilderRingbuffer
_badRingbuffers.Clear(); _badRingbuffers.Clear();
} }
} }

View File

@@ -1,10 +1,9 @@
using Inspectron.Settings; using Inspectron.Settings;
using Inspectron.Settings.Attributes; using Inspectron.Settings.Attributes;
using System.ComponentModel; using Lindt.Colorballs.Duo.Utils;
using System.Security.Cryptography.X509Certificates;
using VisionBuilder.UI.Common; using VisionBuilder.UI.Common;
namespace Lindt.Colorballs.Duo.Utils; namespace VisionBuilder.UI.Ringbuffer;
public class VisionBuilderRingbufferSettings : ISettings public class VisionBuilderRingbufferSettings : ISettings
{ {
@@ -23,48 +22,25 @@ public class VisionBuilderRingbufferSettings : ISettings
[SettingDescription( [SettingDescription(
@" @"
Specifies directory, in which MaxBad and MaxGood are applied. Specifies directory, in which MaxBad and MaxGood are applied.
You can use the following system variables: "+ PathSettingsUtils.INSTUCTIONS
$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
"
)] )]
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))] [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))] [SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
public string GoodImagesPathTemplate { get; set; } = @"\$RECIPE_NAME"; 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))] [SettingPreview(typeof(VisionBuilderRingbufferSettings),nameof(ImagesPathTemplatePreview))]
public string BadImagesPathTemplate { get; set; } = public string BadImagesPathTemplate { get; set; } =
@"\$RECIPE_NAME\$DEFECT_NAME"; @"\$RECIPE_NAME\$DEFECT_NAME";
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))] [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) public void RegisterSettings(InspectronSettings settings)

View 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;
}
}

View File

@@ -13,9 +13,9 @@ using VisionBuilder.UI.Common.RecipeProcessing;
namespace VisionBuilder.UI.Statistics namespace VisionBuilder.UI.Statistics
{ {
public class VisionBuilderStatistics:IInitializable public class VisionBuilderStatistics:IVisionBuilderModule,IDisposable
{ {
private CsvStatistics _csvStatistics; private CsvStatistics? _csvStatistics;
private readonly VisionBuilderStatisticsSettings _settings; private readonly VisionBuilderStatisticsSettings _settings;
private string _filePath; private string _filePath;
private readonly IRecognitionControl _recognitionControl; private readonly IRecognitionControl _recognitionControl;
@@ -29,7 +29,7 @@ namespace VisionBuilder.UI.Statistics
private const string MONTHLY_FILE_NAME = "statistics_month.csv"; private const string MONTHLY_FILE_NAME = "statistics_month.csv";
private const string EMPTY_DATE = ""; private const string EMPTY_DATE = "";
private const string EMPTY_TIME = ""; private const string EMPTY_TIME = "";
public bool DoNotDeleteTempFiles { get; set; } = false;
public VisionBuilderStatistics(VisionBuilderStatisticsSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService) public VisionBuilderStatistics(VisionBuilderStatisticsSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService)
{ {
@@ -37,18 +37,20 @@ namespace VisionBuilder.UI.Statistics
_recognitionControl = recognitionControl; _recognitionControl = recognitionControl;
_loadingService = loadingService; _loadingService = loadingService;
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
_recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
} }
private void _recognitionControl_SessionEnded(SessionEndedEvent obj) private void _recognitionControl_SessionEnded(SessionEndedEvent obj)
{ {
var endedAt = obj.SessionEnded;
_endTime = endedAt;
_loadingService.StartLoading("Writing statistics for " + _settings.CameraName); _loadingService.StartLoading("Writing statistics for " + _settings.CameraName);
try 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.Dispose();
_csvStatistics = null;
WriteFinalStatistics(); WriteFinalStatistics();
} }
finally finally
@@ -67,10 +69,10 @@ namespace VisionBuilder.UI.Statistics
private void _recognitionControl_SessionStarted(SessionStartedEvent obj) private void _recognitionControl_SessionStarted(SessionStartedEvent obj)
{ {
_startTime = DateTime.Now; _startTime = obj.SessionStarted;
_currentRecipe = obj.RecipeName; _currentRecipe = obj.RecipeName;
_fileName = "tmp_"+Guid.NewGuid().ToString()+".csv"; _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); obj.RecipeName, cameraName: _settings.CameraName);
_filePath = Path.Combine(_directoryPath, _fileName); _filePath = Path.Combine(_directoryPath, _fileName);
var headers = new[] { _settings.StartDateColumnName, _settings.StartTimeColumnName, _settings.EndDateColumnName, _settings.EndTimeColumnName, _settings.ErrorTimeColumnName, _settings.ProductColumnName, _settings.ErrorColumnName }; 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(); ConcurrentDictionary<string, int> _defects = new();
private DateTime _endTime;
private void RecordBadImage(string defectName) private void RecordBadImage(string defectName)
{ {
@@ -96,50 +100,60 @@ namespace VisionBuilder.UI.Statistics
_defects.AddOrUpdate(defectName, 1, (key, oldValue) => oldValue + 1); _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 var startDate = _startTime.Date;
if (startDate == DateTime.MinValue || startTime == DateTime.MinValue || endDate == DateTime.MinValue || endTime == DateTime.MinValue) 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); monthStatistics.WriteLine(
throw new InvalidOperationException("Invalid date or time in statistics file: " + filename); [startDate.ToString("d"), startTime.ToString("T"), endDate.ToString("d"), endTime.ToString("T"), _currentRecipe, defect.Key, defect.Value.ToString()]
);
} }
monthStatistics.Dispose();
} }
private void WriteFinalStatistics() private void WriteFinalStatistics()
{ {
try try
{ {
var tmpFiles = Directory.GetFiles(_directoryPath, "tmp_*"); Log.Debug("Writing month statistics");
// order by creation time WriteMonthStatistics();
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);
}
} }
catch (Exception e) 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;
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using Inspectron.Settings; using Inspectron.Settings;
using Inspectron.Settings.Attributes; using Inspectron.Settings.Attributes;
using Lindt.Colorballs.Duo.Utils;
using VisionBuilder.UI.Common; using VisionBuilder.UI.Common;
namespace VisionBuilder.UI.Statistics; namespace VisionBuilder.UI.Statistics;
@@ -16,31 +17,10 @@ public class VisionBuilderStatisticsSettings: ISettings
[SettingDescription( [SettingDescription(
@" @"
Specifies directory, in which MaxBad and MaxGood are applied. Specifies directory, in which MaxBad and MaxGood are applied.
You can use the following system variables: "+PathSettingsUtils.INSTUCTIONS
$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
"
)] )]
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 StartDateColumnName { get; set; } = "Start Date";
public string StartTimeColumnName { get; set; } = "Start time"; 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.ProductColumnName,CameraName+"/"+"Statistics",nameof(ProductColumnName));
settings.RegisterSimple(this,()=>this.ErrorColumnName,CameraName+"/"+"Statistics",nameof(ErrorColumnName)); 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");
}
} }

View File

@@ -14,6 +14,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" /> <ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
<ProjectReference Include="..\VisionBuilder.UI.Statistics\VisionBuilder.UI.Statistics.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View 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;
}
}

View File

@@ -11,14 +11,19 @@ using Inspectron.Settings;
using Inspectron.Settings.Windows.Configuration; using Inspectron.Settings.Windows.Configuration;
using Lindt.Colorballs.Duo.Utils; using Lindt.Colorballs.Duo.Utils;
using Ninject; using Ninject;
using Ninject.Extensions.ChildKernel;
using OpenCvSharp; using OpenCvSharp;
using VisionBuilder.UI.Common.RecipeProcessing; using VisionBuilder.UI.Common.RecipeProcessing;
using VisionBuilder.UI.Console;
using VisionBuilder.UI.Recipes.HawkeyeRecipe; using VisionBuilder.UI.Recipes.HawkeyeRecipe;
using VisionBuilder.UI.Ringbuffer;
using VisionBuilder.UI.Statistics;
namespace VisionBuilder.UI.Windows.Test namespace VisionBuilder.UI.Windows.Test
{ {
internal static class Program internal static class Program
{ {
private const string CAMERA1 = "Camera 1";
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
@@ -29,58 +34,42 @@ namespace VisionBuilder.UI.Windows.Test
// see https://aka.ms/applicationconfiguration. // see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
StandardKernel kernelCamera1 = new StandardKernel();
// SETTINGS //
InspectronSettings settings = new InspectronSettings("..\\Config\\AppSettings.xml"); 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(); kernelCamera1.RegisterSettings();
settings.LoadSettings(); 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.InitializeModules();
kernelCamera1.Bind<ISettingsService>().To<SettingsService>().InSingletonScope();
kernelCamera1.Bind<IRecognitionControl>().To<HawkeyeRecognitionControl>().InSingletonScope();
kernelCamera1.Bind<VisionBuilderRingbuffer>().ToSelf().InSingletonScope();
kernelCamera1.Bind<ILoadingService>().To<LoadingService>().InSingletonScope();
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(); settings.LoadSettings();
Application.Run(new Form1(mainWindowVm)); Application.Run(new Form1(mainWindowVm));

View File

@@ -9,6 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.6.0.20220608" /> <PackageReference Include="OpenCvSharp4.runtime.win" Version="4.6.0.20220608" />
</ItemGroup> </ItemGroup>
@@ -17,9 +18,12 @@
<ProjectReference Include="..\framework\Inspectron.Settings\Inspectron.Settings.csproj" /> <ProjectReference Include="..\framework\Inspectron.Settings\Inspectron.Settings.csproj" />
<ProjectReference Include="..\framework\MaterialSkin.Core\MaterialSkin.Core.csproj" /> <ProjectReference Include="..\framework\MaterialSkin.Core\MaterialSkin.Core.csproj" />
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.Emulation\Hawkeye.VisionBuilder.UI.Sources.Emulation.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.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.Recipes.HawkeyeRecipe\VisionBuilder.UI.Recipes.HawkeyeRecipe.csproj" />
<ProjectReference Include="..\VisionBuilder.UI.Ringbuffer\VisionBuilder.UI.Ringbuffer.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" /> <ProjectReference Include="..\VisionBuilder.UI.Windows\VisionBuilder.UI.Windows.csproj" />
</ItemGroup> </ItemGroup>

View 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>

View File

@@ -31,7 +31,7 @@ namespace VisionBuilder.UI.Windows.Components
btnStop.Command=_singleCameraVm.StopCommand; btnStop.Command=_singleCameraVm.StopCommand;
btnSelectRecipe.Command=_singleCameraVm.SelectRecipeCommand; 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); btnSelectRecipe.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CurrentRecipeName), true, DataSourceUpdateMode.OnPropertyChanged);
} }

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -5,10 +5,10 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
namespace VisionBuilder.UI.Windows.Settings; namespace VisionBuilder.UI.Windows.Settings;
public class ImagePreviewService: IImagePreviewService public class WindowsImagePreviewService: IImagePreviewService
{ {
public ImagePreviewService() public WindowsImagePreviewService()
{ {
} }

View 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);
}
}
}

View File

@@ -4,7 +4,7 @@ using VisionBuilder.UI.Windows.Dialogs;
namespace VisionBuilder.UI.Windows.Settings; namespace VisionBuilder.UI.Windows.Settings;
public class RecipeSelectionDialogService: IRecipeSelectionDialogService public class WindowsRecipeSelectionDialogService: IRecipeSelectionDialogService
{ {
public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm) public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm)
{ {

View File

@@ -5,12 +5,12 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
namespace VisionBuilder.UI.Windows.Settings; namespace VisionBuilder.UI.Windows.Settings;
public class SettingsService: ISettingsService public class WindowsSettingsService: ISettingsService
{ {
private readonly UIConfiguration _uiConfiguration; private readonly UIConfiguration _uiConfiguration;
private readonly InspectronSettings _settings; private readonly InspectronSettings _settings;
public SettingsService(UIConfiguration uiConfiguration, InspectronSettings settings) public WindowsSettingsService(UIConfiguration uiConfiguration, InspectronSettings settings)
{ {
_uiConfiguration = uiConfiguration ?? throw new ArgumentNullException(nameof(uiConfiguration)); _uiConfiguration = uiConfiguration ?? throw new ArgumentNullException(nameof(uiConfiguration));
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); _settings = settings ?? throw new ArgumentNullException(nameof(settings));

View File

@@ -51,6 +51,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.Fastbuffer", "fr
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{C828783C-1CE1-4245-8731-4AE18A28F590}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{C828783C-1CE1-4245-8731-4AE18A28F590}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -155,6 +173,9 @@ Global
{51C2BC65-5E5D-E72A-988D-215E192DE889} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1} {51C2BC65-5E5D-E72A-988D-215E192DE889} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
{1F9BB3BD-65C3-4B96-A880-4908CF6D9754} = {C828783C-1CE1-4245-8731-4AE18A28F590} {1F9BB3BD-65C3-4B96-A880-4908CF6D9754} = {C828783C-1CE1-4245-8731-4AE18A28F590}
{0A23E56A-DF4C-5174-18DF-18A771957AF4} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1} {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677} SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}

View File

@@ -28,7 +28,8 @@ public class DefaultControlFactory : IControlFactory
getPreview = value => (string)method.Invoke(null, new[] { value }); getPreview = value => (string)method.Invoke(null, new[] { value });
previewLabel = new Label previewLabel = new Label
{ {
AutoSize = true, AutoSize = false,
Size = new System.Drawing.Size(600, 30),
Padding = new Padding(15, 0, 0, 0), Padding = new Padding(15, 0, 0, 0),
Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Italic), Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Italic),
Text = getPreview(initialValue) Text = getPreview(initialValue)
@@ -95,7 +96,7 @@ public class DefaultControlFactory : IControlFactory
private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback, private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback,
string settingDescription, Label previewLabel, Func<object, string> getPreview) 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) }; var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
textBox.TextChanged += (s, e) => textBox.TextChanged += (s, e) =>

View File

@@ -33,6 +33,7 @@ public partial class OptionsWindow
flowLayoutPanelSettings.FlowDirection = FlowDirection.TopDown; flowLayoutPanelSettings.FlowDirection = FlowDirection.TopDown;
flowLayoutPanelSettings.Location = new Point(220, 12); flowLayoutPanelSettings.Location = new Point(220, 12);
flowLayoutPanelSettings.Name = "flowLayoutPanelSettings"; flowLayoutPanelSettings.Name = "flowLayoutPanelSettings";
flowLayoutPanelSettings.Padding = new Padding(0, 0, 0, 15);
flowLayoutPanelSettings.Size = new Size(852, 582); flowLayoutPanelSettings.Size = new Size(852, 582);
flowLayoutPanelSettings.TabIndex = 1; flowLayoutPanelSettings.TabIndex = 1;
flowLayoutPanelSettings.WrapContents = false; flowLayoutPanelSettings.WrapContents = false;

View File

@@ -42,7 +42,7 @@ public class CsvStatistics
{ {
_csv.Dispose(); _csv.Dispose();
_stream.Dispose(); _stream.Dispose();
_fileStream.Close();
_fileStream.Dispose(); _fileStream.Dispose();
} }
} }

View File

@@ -2,5 +2,6 @@
public class StatisticsRecord public class StatisticsRecord
{ {
public DateTime Time { get; set; }
public string[] Values { get; set; } public string[] Values { get; set; }
} }

View File

@@ -38,6 +38,7 @@
ControlBox = false; ControlBox = false;
Name = "MaterialLoader"; Name = "MaterialLoader";
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
TopMost = true;
ResumeLayout(false); ResumeLayout(false);
} }