chore: remove GSS2.Test project

This commit is contained in:
2026-05-13 12:21:59 +03:00
parent c7a798457d
commit 9f37ca9610
15 changed files with 0 additions and 1308 deletions
-16
View File
@@ -1,16 +0,0 @@
<Application
x:Class="GSS2.Test.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GSS2.Test"
RequestedThemeVariant="Dark">
<Application.Styles>
<FluentTheme />
</Application.Styles>
<Application.DataTemplates>
<local:DependencyInjectionViewLocator />
</Application.DataTemplates>
</Application>
-72
View File
@@ -1,72 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using GSS2.Test.ViewModels;
using GSS2.Test.Views;
using GSS2.UI.Core;
using GSS2.UI.Core.Services;
using GSS2.UI.Core.ViewModels;
namespace GSS2.Test;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is null)
throw new InvalidOperationException("Application framework initialization cannot be fulfilled, ApplicationLifetime is null");
var loggerFactory = Program.ApplicationHost.Services.GetRequiredService<ILoggerFactory>();
var navigationService = Program.ApplicationHost.Services.GetRequiredService<NavigationService>();
switch (ApplicationLifetime)
{
case IClassicDesktopStyleApplicationLifetime desktop:
Console.WriteLine("Application running in classic style");
Console.WriteLine($"Application running in {(Program.IsFullscreen ? "fullscreen" : "window")} mode");
desktop.Startup += (_, _) => Program.ApplicationHost.RunAsync();
desktop.Exit += (_, _) => Program.ApplicationHost.StopAsync();
var mainWindowViewModelLogger = loggerFactory.CreateLogger<MainWindowViewModel>();
var mainWindow = new MainWindow()
{
DataContext = new MainWindowViewModel(mainWindowViewModelLogger)
{
SystemDecorations = Program.IsFullscreen ? Avalonia.Controls.SystemDecorations.None : Avalonia.Controls.SystemDecorations.Full,
Topmost = Program.IsFullscreen,
WindowState = Program.IsFullscreen ? Avalonia.Controls.WindowState.FullScreen : Avalonia.Controls.WindowState.Normal
},
};
desktop.MainWindow = mainWindow;
Program.ShutdownRequested += (_, _) => Dispatcher.UIThread.Invoke(() => desktop.MainWindow?.Close());
break;
case ISingleViewApplicationLifetime singleView:
Console.WriteLine("Application running in single view style");
break;
default:
throw new InvalidOperationException($"Application framework initialization cannot be fulfilled, ApplicationLifetime has unknown type inherited from: {string.Join(", ", ApplicationLifetime.GetType().GetInterfaces().Select(i => i.FullName))}");
}
var mainViewLogger = loggerFactory.CreateLogger<MainView>();
var mainView = new MainView(mainViewLogger)
{
DataContext = Program.ApplicationHost.Services.GetRequiredService<MainViewModel>()
};
navigationService.NavigateTo(mainView);
base.OnFrameworkInitializationCompleted();
}
}
-25
View File
@@ -1,25 +0,0 @@
using GSS2.UI.Core.Views;
using GSS2.UI.Core.ViewModels;
using GSS2.Test.Views;
using GSS2.Test.ViewModels;
using GSS2.UI.Core.Services;
using Avalonia;
namespace GSS2.Test;
public static class DependencyInjectionHelper
{
public static IServiceCollection AddViewsAndModels(this IServiceCollection services) =>
services
.AddSingleton<NavigationService>((_) => new NavigationService(Application.Current?.ApplicationLifetime))
.AddSingleton<MainWindowViewModel>()
.AddSingleton<MainView>()
.AddSingleton<MainViewModel>()
.AddSingleton<ResourcesView>()
.AddSingleton<ResourcesViewModel>()
.AddSingleton<CameraView>()
.AddSingleton<CameraViewModel>()
.AddSingleton<IlluminatorControllerView>()
.AddSingleton<IlluminatorControllerViewModel>();
}
@@ -1,33 +0,0 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using GSS2.UI.Core.ViewModels;
namespace GSS2.Test;
public class DependencyInjectionViewLocator : IDataTemplate
{
public Control? Build(object? data)
{
if (data is null)
return null;
var name = data.GetType().AssemblyQualifiedName?
.Replace("ViewModels", "Views")
.Replace("ViewModel", "View");
if (name is null)
return new TextBlock { Text = $"Not found: {data}" };
var type = Type.GetType(name);
if (type is null)
return new TextBlock { Text = $"Not found type: {name} for {data}" };
var control = Program.ApplicationHost.Services.GetRequiredService(type) as Control;
if (control is null)
return new TextBlock { Text = $"Not found in DI: {type} for {data}" };
control.DataContext = data;
return control;
}
public bool Match(object? data) => data is ViewModelBase;
}
-33
View File
@@ -1,33 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-GSS2.Test-191ece0a-855d-46ec-9ecb-44378fc3db83</UserSecretsId>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.LinuxFramebuffer" Version="12.0.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Avalonia" Version="12.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="12.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.0" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="System.CommandLine" Version="2.0.3" />
<!-- Avalonia.X11 зависит от Avalonia.FreeDesktop который зависит от Tmds.DBus.Protocol==0.90.3 -->
<!-- Tmds.DBus.Protocol==0.90.3 имеет критическую уязвмость, поэтому превентивно установлена версия с исправлением -->
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GSS2.Core\GSS2.Core.csproj" />
<ProjectReference Include="..\GSS2.UI.Core\GSS2.UI.Core.csproj" />
</ItemGroup>
</Project>
-189
View File
@@ -1,189 +0,0 @@
using Avalonia;
using Avalonia.OpenGL.Egl;
using System.CommandLine;
namespace GSS2.Test;
public partial class Program
{
private enum RenderingMode
{
X11,
DRM
}
private static List<string>? _args = null;
private static readonly RootCommand _rootCommand = new("Run UI application");
private static readonly Option<bool> _fullscreen = new("--fullscreen")
{
Description = "Run in fullscreen mode"
};
private static readonly Option<RenderingMode> _renderingMode = new("--rendering-mode")
{
Description = "Rendering mode to use",
DefaultValueFactory = (_) => RenderingMode.X11
};
private static readonly Option<bool> _hideConsole = new("--hide-console")
{
Description = "Suppress console output",
DefaultValueFactory = (result) => result.GetRequiredValue(_renderingMode) == RenderingMode.DRM
};
private static readonly Command _cameraTuningCommand = new("camera-tuning")
{
Description = "Tune camera parameters"
};
private static readonly Option<FileInfo> _cameraTuningOutputFile = new("--output")
{
Description = "Output image file",
Required = false
};
private static readonly Option<double> _cameraTuningGainR = new("--gain-r")
{
Description = "Color gain (red channel) to start from",
DefaultValueFactory = (_) => 1
};
private static readonly Option<double> _cameraTuningGainG = new("--gain-g")
{
Description = "Color gain (green channel) to start from",
DefaultValueFactory = (_) => 1
};
private static readonly Option<double> _cameraTuningGainB = new("--gain-b")
{
Description = "Color gain (blue channel) to start from",
DefaultValueFactory = (_) => 1
};
private static readonly Option<double> _cameraTuningThreshold = new("--threshold")
{
Description = "Threshold to stop on",
DefaultValueFactory = (_) => 0.1
};
private static readonly Option<uint> _cameraTuningSteps = new("--steps")
{
Description = "Maximum steps count",
DefaultValueFactory = (_) => 10
};
private static readonly Command _captureCommand = new("capture")
{
Description = "Capture image from camera"
};
private static readonly Option<FileInfo> _captureOutputFile = new("--output")
{
Description = "Output image file",
DefaultValueFactory = (_) => new FileInfo("image.png")
};
private static readonly Option<double> _captureIntensityWhite = new("--intensity-white")
{
Description = "Illuminator intensity for white (from 0 to 1)",
DefaultValueFactory = (_) => 1
};
private static readonly Option<double> _captureIntensityUv365nm = new("--intensity-uv365nm")
{
Description = "Illuminator intensity for UV 365 nm (from 0 to 1)",
DefaultValueFactory = (_) => 0
};
private static readonly Option<double> _captureIntensityUv254nm = new("--intensity-uv254nm")
{
Description = "Illuminator intensity for UV 254 nm (from 0 to 1)",
DefaultValueFactory = (_) => 0
};
private static readonly Command _imageStorageClear = new("image-storage-clear")
{
Description = "Clear image storage from unused images"
};
public static event ConsoleCancelEventHandler? ShutdownRequested;
[STAThread]
public static int Main(string[] args)
{
_args = args.ToList();
#if DEBUG
WaitDebuggerIfNeeded();
#endif
_rootCommand.SetAction(Ui);
_rootCommand.Add(_fullscreen);
_rootCommand.Add(_renderingMode);
_rootCommand.Add(_hideConsole);
_rootCommand.Add(_cameraTuningCommand);
_cameraTuningCommand.SetAction(CameraTuning);
_cameraTuningCommand.Add(_cameraTuningOutputFile);
_cameraTuningCommand.Add(_cameraTuningGainR);
_cameraTuningCommand.Add(_cameraTuningGainG);
_cameraTuningCommand.Add(_cameraTuningGainB);
_cameraTuningCommand.Add(_cameraTuningThreshold);
_cameraTuningCommand.Add(_cameraTuningSteps);
_rootCommand.Add(_captureCommand);
_captureCommand.SetAction(Capture);
_captureCommand.Add(_captureOutputFile);
_captureCommand.Add(_captureIntensityWhite);
_captureCommand.Add(_captureIntensityUv365nm);
_captureCommand.Add(_captureIntensityUv254nm);
_rootCommand.Add(_imageStorageClear);
_imageStorageClear.SetAction(ImageStorageClear);
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnProgramShutdown);
Console.WriteLine("Press Ctrl+C to shut down.");
var parseResult = _rootCommand.Parse(_args);
if (parseResult.Errors.Count() != 0)
{
foreach (var parseError in parseResult.Errors)
Console.Error.WriteLine(parseError.Message);
return 1;
}
return parseResult.Invoke();
}
private static void OnProgramShutdown(object? sender, ConsoleCancelEventArgs ea)
{
ShutdownRequested?.Invoke(sender, ea);
ea.Cancel = true;
}
private static void WaitDebuggerIfNeeded()
{
if (System.Diagnostics.Debugger.IsAttached)
return;
if (_args is null || !_args.Contains("--debug"))
return;
_args.Remove("--debug");
bool keepWaiting = true;
void CancelHandle(object? sender, ConsoleCancelEventArgs e)
{
e.Cancel = true;
keepWaiting = false;
Console.WriteLine("\nWait cancelled by user.");
Environment.Exit(0);
}
Console.CancelKeyPress += CancelHandle;
Console.WriteLine("Waiting debugger attach (Press Ctrl+C to cancel)");
Console.WriteLine($"Process Id: {Environment.ProcessId}");
Console.WriteLine($"Process Name: {Environment.ProcessPath}");
Console.WriteLine($"Thread Id: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Thread Name: {Thread.CurrentThread.Name}");
while (!System.Diagnostics.Debugger.IsAttached && keepWaiting)
Thread.Sleep(100);
if (System.Diagnostics.Debugger.IsAttached)
System.Diagnostics.Debugger.Break();
Console.CancelKeyPress -= CancelHandle;
}
}
-330
View File
@@ -1,330 +0,0 @@
using System.CommandLine;
using GSS2.Core;
using GSS2.Core.Hardware;
using GSS2.Core.Logging;
using Microsoft.Extensions.Logging.Console;
using OpenCvSharp;
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
namespace GSS2.Test;
public partial class Program
{
private class CameraTuningWorker : BackgroundService
{
private readonly ILogger<CameraTuningWorker> _logger;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly CameraService _cameraService;
private readonly IlluminatorService _illuminatorService;
private readonly FileInfo? _outputFile;
private double _gainR;
private double _gainG;
private double _gainB;
private readonly double _threshold;
private readonly uint _steps;
private static readonly Scalar[] KnownSegments =
{
new Scalar(0,255,255), // Cyan
new Scalar(0,0,255), // Blue
new Scalar(255,0,255), // Magenta
new Scalar(128,128,128), // Gray (skip)
new Scalar(255,0,0), // Red
new Scalar(255,128,0), // Orange
new Scalar(255,255,0), // Yellow
new Scalar(0,255,0) // Green
};
private const double PatternOuterRadius = 180.0;
private const double PatternCenterRadius = 60.0;
public CameraTuningWorker(ILogger<CameraTuningWorker> logger, IHostApplicationLifetime applicationLifetime, CameraService cameraService, IlluminatorService illuminatorService, FileInfo? outputFile, double gainR, double gainG, double gainB, double threshold, uint steps)
{
_applicationLifetime = applicationLifetime;
_logger = logger;
_cameraService = cameraService;
_illuminatorService = illuminatorService;
_outputFile = outputFile;
_gainR = gainR;
_gainG = gainG;
_gainB = gainB;
_threshold = threshold;
_steps = steps;
_logger.LogInformation("Initialized");
_logger.LogInformation("Output File: {}", _outputFile);
_logger.LogInformation("Gain R: {}", _gainR);
_logger.LogInformation("Gain G: {}", _gainG);
_logger.LogInformation("Gain B: {}", _gainB);
_logger.LogInformation("Threshold: {}", _threshold);
_logger.LogInformation("Steps: {}", _steps);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Running camera fine tuning");
Mat? image;
int i = 0;
bool optimalGainsFound = false;
_illuminatorService.SetIntensity(1, 0, 0);
_illuminatorService.TurnOn();
while (i < _steps)
{
_cameraService.Configuration.DecodeGainR = _gainR;
_cameraService.Configuration.DecodeGainG = _gainG;
_cameraService.Configuration.DecodeGainB = _gainB;
try
{
image = await _cameraService.CaptureImage(stoppingToken, 2);
if (image is null)
{
_logger.LogError("Cannot get image");
break;
}
if (image.Type() != MatType.CV_8UC3 &&
image.Type() != MatType.CV_8UC4 &&
image.Type() != MatType.CV_16UC3 &&
image.Type() != MatType.CV_16UC4)
{
_logger.LogCritical("Unsupported image type: {}", image.Type().ToString());
break;
}
if (image.Type() == MatType.CV_16UC3)
image.ConvertTo(image, MatType.CV_8UC3, 1 / 256.0);
if (image.Type() == MatType.CV_16UC4)
image.ConvertTo(image, MatType.CV_8UC4, 1 / 256.0);
var channels = image.Split();
Cv2.Merge([channels[0], channels[1], channels[2]], image);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred while image capturing");
break;
}
try
{
if (_outputFile is not null)
image.ImWrite(_outputFile.FullName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred while image saving");
}
try
{
var gains = ComputeWhiteBalanceGains(image);
if (gains is null)
{
_logger.LogError("Cannot compute gains");
break;
}
var (r, g, b) = gains.Value;
if (Math.Abs(_gainR - r) < _threshold &&
Math.Abs(_gainG - g) < _threshold &&
Math.Abs(_gainB - b) < _threshold)
{
_logger.LogInformation("Optimal color gains: r={}, g={}, b:={}", r, g, b);
optimalGainsFound = true;
break;
}
_gainR = (_gainR + r) / 2;
_gainG = (_gainG + g) / 2;
_gainB = (_gainB + b) / 2;
_logger.LogInformation("Step {}/{} color gains: r={}, g={}, b:={}", i + 1, _steps, _gainR, _gainG, _gainB);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred while gains computing");
break;
}
i++;
}
_illuminatorService.TurnOff();
if (!optimalGainsFound)
_logger.LogWarning("Optimal gains not found");
_applicationLifetime.StopApplication();
}
private (double rGain, double gGain, double bGain)? ComputeWhiteBalanceGains(Mat image)
{
var circle = FindCircle(image);
if (circle == null)
return null;
var (rx, ry, rr) = circle.Value;
double scale = rr / PatternOuterRadius;
double innerPx = PatternCenterRadius * scale;
int midR = (int)((innerPx + rr) / 2.0);
double phi0 = DetectOrientation(image, rx, ry, rr);
if (double.IsNaN(phi0))
phi0 = Math.PI / 2.0;
var samples = new List<Vec3d>();
var refs = new List<Vec3d>();
int N = KnownSegments.Length;
for (int i = 0; i < N; i++)
{
if (i == 3) continue; // skip gray
double theta = phi0 - (2 * Math.PI / N) * i;
int cx = (int)(rx + midR * Math.Cos(theta));
int cy = (int)(ry - midR * Math.Sin(theta));
var roi = new Rect(
Math.Max(cx - 8, 0),
Math.Max(cy - 8, 0),
Math.Min(16, image.Width - Math.Max(cx - 8, 0)),
Math.Min(16, image.Height - Math.Max(cy - 8, 0)));
if (roi.Width < 8 || roi.Height < 8)
continue;
using var patch = new Mat(image, roi);
var mean = Cv2.Mean(patch);
// BGR → RGB
samples.Add(new Vec3d(mean.Val2, mean.Val1, mean.Val0));
var refColor = KnownSegments[i];
refs.Add(new Vec3d(refColor.Val2, refColor.Val1, refColor.Val0));
}
if (samples.Count < 3)
return null;
// Формируем матрицы для least squares
var S = new Mat(samples.Count, 3, MatType.CV_64F);
var R = new Mat(samples.Count, 3, MatType.CV_64F);
for (int i = 0; i < samples.Count; i++)
{
S.Set(i, 0, samples[i][0]);
S.Set(i, 1, samples[i][1]);
S.Set(i, 2, samples[i][2]);
R.Set(i, 0, refs[i][0]);
R.Set(i, 1, refs[i][1]);
R.Set(i, 2, refs[i][2]);
}
var M = new Mat();
Cv2.Solve(S, R, M, DecompTypes.Normal);
double rGain = Math.Max(M.At<double>(0, 0), 1.0);
double gGain = 1.0;
double bGain = Math.Max(M.At<double>(2, 2), 1.0);
return (rGain, gGain, bGain);
}
private (int x, int y, int r)? FindCircle(Mat image)
{
using var gray = new Mat();
Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
Cv2.GaussianBlur(gray, gray, new Size(9, 9), 2);
var circles = Cv2.HoughCircles(
gray,
HoughModes.Gradient,
dp: 1.2,
minDist: gray.Rows / 3,
param1: 100,
param2: 30,
minRadius: (int)(gray.Rows * 0.3),
maxRadius: (int)(gray.Rows * 0.6));
if (circles.Length == 0)
return null;
var c = circles[0];
return ((int)c.Center.X, (int)c.Center.Y, (int)c.Radius);
}
private double DetectOrientation(Mat image, int rx, int ry, int rr)
{
double scale = rr / PatternOuterRadius;
int innerR = (int)(PatternCenterRadius * scale);
using var gray = new Mat();
Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
using var mask = Mat.Zeros(image.Size(), MatType.CV_8U).ToMat();
Cv2.Circle(mask, new Point(rx, ry), innerR, Scalar.White, -1);
var whitePoints = new List<Point>();
for (int y = 0; y < gray.Rows; y++)
{
for (int x = 0; x < gray.Cols; x++)
{
if (mask.At<byte>(y, x) == 255 &&
gray.At<byte>(y, x) > 128)
{
whitePoints.Add(new Point(x, y));
}
}
}
if (whitePoints.Count < 20)
return double.NaN;
double cx = whitePoints.Average(p => p.X);
double cy = whitePoints.Average(p => p.Y);
return Math.Atan2(ry - cy, cx - rx);
}
}
private static void CameraTuning(ParseResult parseResult)
{
var outputFile = parseResult.GetValue(_cameraTuningOutputFile);
var gainR = parseResult.GetRequiredValue(_cameraTuningGainR);
var gainG = parseResult.GetRequiredValue(_cameraTuningGainG);
var gainB = parseResult.GetRequiredValue(_cameraTuningGainB);
var threshold = parseResult.GetRequiredValue(_cameraTuningThreshold);
var steps = parseResult.GetRequiredValue(_cameraTuningSteps);
var builder = Host.CreateApplicationBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
builder.Logging.AddProvider(new FileLoggerProvider("full.log", true));
builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false));
builder.Services.AddSingleton<LibCameraLogSink>();
builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddHostedService<CameraTuningWorker>(services =>
{
var logger = services.GetRequiredService<ILogger<CameraTuningWorker>>();
var applicationLifetime = services.GetRequiredService<IHostApplicationLifetime>();
var cameraService = services.GetRequiredService<CameraService>();
var illuminatorService = services.GetRequiredService<IlluminatorService>();
return new CameraTuningWorker(logger, applicationLifetime, cameraService, illuminatorService, outputFile, gainB, gainG, gainR, threshold, steps);
});
var host = builder.Build();
host.Services.GetRequiredService<LibCameraLogSink>();
host.Run();
}
}
-122
View File
@@ -1,122 +0,0 @@
using System.CommandLine;
using GSS2.Core;
using GSS2.Core.Hardware;
using GSS2.Core.Logging;
using Microsoft.Extensions.Logging.Console;
using OpenCvSharp;
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
namespace GSS2.Test;
public partial class Program
{
private class CaptureWorker : BackgroundService
{
private readonly ILogger<CaptureWorker> _logger;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly CameraService _cameraService;
private readonly IlluminatorService _illuminatorService;
private readonly FileInfo _outputFile;
private readonly double _intensityWhite;
private readonly double _intensityUv365nm;
private readonly double _intensityUv254nm;
public CaptureWorker(ILogger<CaptureWorker> logger, IHostApplicationLifetime applicationLifetime, CameraService cameraService, IlluminatorService illuminatorService, FileInfo outputFile, double intensityWhite, double intensityUv365nm, double intensityUv254nm)
{
_applicationLifetime = applicationLifetime;
_logger = logger;
_cameraService = cameraService;
_illuminatorService = illuminatorService;
_outputFile = outputFile;
_intensityWhite = intensityWhite;
_intensityUv365nm = intensityUv365nm;
_intensityUv254nm = intensityUv254nm;
_logger.LogInformation("Initialized");
_logger.LogInformation("Output File: {}", _outputFile);
_logger.LogInformation("Intensity White: {}", _intensityWhite);
_logger.LogInformation("Intensity UV 365 nm: {}", _intensityUv365nm);
_logger.LogInformation("Intensity UV 254 nm: {}", _intensityUv254nm);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Running camera fine tuning");
_illuminatorService.SetIntensity(_intensityWhite, _intensityUv365nm, _intensityUv254nm);
_illuminatorService.TurnOn();
Mat? image;
try
{
image = await _cameraService.CaptureImage(stoppingToken, 5);
if (image is null)
{
_logger.LogCritical("Cannot get image");
_applicationLifetime.StopApplication();
return;
}
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Exception occurred while image capturing");
_applicationLifetime.StopApplication();
return;
}
finally
{
_illuminatorService.TurnOff();
}
try
{
image.ImWrite(_outputFile.FullName);
_logger.LogInformation("Image saved: {}", _outputFile.FullName);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Exception occurred while image saving");
}
_applicationLifetime.StopApplication();
}
}
private static void Capture(ParseResult parseResult)
{
var outputFile = parseResult.GetRequiredValue(_captureOutputFile);
var intensityWhite = parseResult.GetRequiredValue(_captureIntensityWhite);
var intensityUv365nm = parseResult.GetRequiredValue(_captureIntensityUv365nm);
var intensityUv254nm = parseResult.GetRequiredValue(_captureIntensityUv254nm);
var builder = Host.CreateApplicationBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
builder.Logging.AddProvider(new FileLoggerProvider("full.log", true));
builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false));
builder.Services.AddSingleton<LibCameraLogSink>();
builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddHostedService<CaptureWorker>(services =>
{
var logger = services.GetRequiredService<ILogger<CaptureWorker>>();
var applicationLifetime = services.GetRequiredService<IHostApplicationLifetime>();
var cameraService = services.GetRequiredService<CameraService>();
var illuminatorService = services.GetRequiredService<IlluminatorService>();
return new CaptureWorker(logger, applicationLifetime, cameraService, illuminatorService, outputFile, intensityWhite, intensityUv365nm, intensityUv254nm);
});
var host = builder.Build();
host.Services.GetRequiredService<LibCameraLogSink>();
host.Run();
}
}
-102
View File
@@ -1,102 +0,0 @@
using System.CommandLine;
using GSS2.Core;
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
using GSS2.Core.Hardware;
using GSS2.Core.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Console;
using OpenCvSharp;
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
namespace GSS2.Test;
public partial class Program
{
private class ImagesStorageClearWorker : BackgroundService
{
private readonly ILogger<ImagesStorageClearWorker> _logger;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly AmineContentCalibrationContext _context;
private readonly ImageStorageService _imageStorageService;
public ImagesStorageClearWorker(ILogger<ImagesStorageClearWorker> logger, IHostApplicationLifetime applicationLifetime, AmineContentCalibrationContext context, ImageStorageService imageStorageService)
{
_logger = logger;
_applicationLifetime = applicationLifetime;
_context = context;
_imageStorageService = imageStorageService;
_logger.LogInformation("Initialized");
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Running images storage clear");
var recordsFiles = _context.ImageRecords.Select(r => r.ImagePath);
var storedFiles = _imageStorageService.ListFiles()
.Select(Path.GetFileName)
.OfType<string>();
var unusedFiles = storedFiles.Except(recordsFiles)
.ToList()
.Order();
if (unusedFiles is null || unusedFiles.Count() == 0)
{
_logger.LogInformation("Unused images not found");
_applicationLifetime.StopApplication();
return;
}
_logger.LogInformation("Found {} unused image(s):\n\t{}", unusedFiles.Count(), string.Join("\n\t", unusedFiles));
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Delete this files? (y/n) ");
var input = Console.ReadLine()?.Trim();
if (input == "y")
{
foreach (var file in unusedFiles)
_imageStorageService.Delete(file);
break;
}
if (input == "n")
break;
}
_applicationLifetime.StopApplication();
}
}
private static void ImageStorageClear(ParseResult parseResult)
{
var builder = Host.CreateApplicationBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
builder.Logging.AddProvider(new FileLoggerProvider("full.log", true));
builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false));
builder.Services.AddSingleton<LibCameraLogSink>();
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
builder.Services.AddImageStorageService(new DirectoryInfo("images"));
builder.Services.AddHostedService<ImagesStorageClearWorker>();
var host = builder.Build();
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
context.Database.Migrate();
}
host.Run();
}
}
-110
View File
@@ -1,110 +0,0 @@
using System.CommandLine;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Console;
using Avalonia;
using Avalonia.OpenGL.Egl;
using GSS2.Core;
using GSS2.Core.Logging;
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
namespace GSS2.Test;
public partial class Program
{
public static IHost ApplicationHost { get; } = BuildHostApp();
public static bool IsFullscreen = false;
private static void Ui(ParseResult parseResult)
{
var builder = BuildAvaloniaApp();
var fullscreen = parseResult.GetValue(_fullscreen);
var renderingMode = parseResult.GetValue(_renderingMode);
var hideConsole = parseResult.GetValue(_hideConsole);
IsFullscreen = fullscreen;
if (hideConsole)
SilenceConsole();
switch (renderingMode)
{
case RenderingMode.X11:
builder.StartWithClassicDesktopLifetime(_args?.ToArray() ?? []);
break;
case RenderingMode.DRM:
builder.StartLinuxDrm(_args?.ToArray() ?? [], "/dev/dri/card1", 1.0);
break;
}
}
private static void SilenceConsole()
{
new Thread(() =>
{
Console.CursorVisible = false;
while (true)
Console.ReadKey(true);
})
{ IsBackground = true }.Start();
}
private static IHost BuildHostApp()
{
var builder = Host.CreateApplicationBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
builder.Logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
builder.Logging.AddProvider(new FileLoggerProvider("full.log", true));
builder.Logging.AddProvider(new FileLoggerProvider("last_run.log", false));
builder.Services.AddSingleton<LibCameraLogSink>();
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
builder.Services.AddAmineContentResultsContext(builder.Configuration);
builder.Services.AddLightsService("Hardware:Lights");
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddComputeResourcesService();
builder.Services.AddImageStorageService(new DirectoryInfo("images"));
builder.Services.AddViewsAndModels();
var host = builder.Build();
host.Services.GetRequiredService<LibCameraLogSink>();
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentCalibrationContext>();
context.Database.Migrate();
}
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AmineContentResultsContext>();
context.Database.Migrate();
}
return host;
}
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseSkia()
.LogToTrace()
.With(new X11PlatformOptions
{
RenderingMode = [X11RenderingMode.Egl]
})
.With(new EglDisplayOptions
{
SupportsContextSharing = true
});
}
-61
View File
@@ -1,61 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GSS2.Core.Hardware;
using GSS2.UI.Core.ViewModels;
namespace GSS2.Test.ViewModels;
public partial class MainViewModel : ViewModelBase
{
private readonly ILogger<MainViewModel> _logger;
private readonly CameraService _cameraService;
private readonly IlluminatorService _illuminatorService;
[ObservableProperty] private CameraViewModel _cameraViewModel;
[ObservableProperty] private ResourcesViewModel _resourcesViewModel;
[ObservableProperty] private IlluminatorControllerViewModel _illuminatorControllerViewModel;
[ObservableProperty] private OpenCvSharp.Mat? _image1;
[ObservableProperty] private OpenCvSharp.Mat? _image2;
[ObservableProperty] private OpenCvSharp.Mat? _image3;
[ObservableProperty] private OpenCvSharp.Mat? _image4;
[ObservableProperty] private IRelayCommand _buttonCaptureClick;
[ObservableProperty] private IRelayCommand _buttonUpClick;
[ObservableProperty] private IRelayCommand _buttonDownClick;
[ObservableProperty] private int _currentIndex;
[ObservableProperty] private bool _canScrollForward;
[ObservableProperty] private bool _canScrollBackward;
public MainViewModel(ILogger<MainViewModel> logger, CameraViewModel cameraViewModel, ResourcesViewModel resourcesViewModel, IlluminatorControllerViewModel illuminatorControllerViewModel, CameraService cameraService, IlluminatorService illuminatorService)
{
_logger = logger;
_logger.LogInformation("Initialization");
_cameraService = cameraService;
_illuminatorService = illuminatorService;
CameraViewModel = cameraViewModel;
ResourcesViewModel = resourcesViewModel;
IlluminatorControllerViewModel = illuminatorControllerViewModel;
ButtonCaptureClick = new RelayCommand(() =>
{
Task.Run(async () =>
{
_illuminatorService.TurnOn();
_illuminatorService.SetIntensity(1, 0, 0);
await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image1 = image.Result);
_illuminatorService.SetIntensity(0, 1, 1);
await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image2 = image.Result);
_illuminatorService.SetIntensity(0, 1, 0);
await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image3 = image.Result);
_illuminatorService.SetIntensity(0, 0, 1);
await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image4 = image.Result);
_illuminatorService.SetIntensity(0, 0, 0);
_illuminatorService.TurnOff();
});
});
ButtonUpClick = new RelayCommand(() => CurrentIndex--);
ButtonDownClick = new RelayCommand(() => CurrentIndex++);
_logger.LogInformation("Initialized");
}
}
-100
View File
@@ -1,100 +0,0 @@
<UserControl
x:Class="GSS2.Test.Views.MainView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="using:GSS2.UI.Core"
xmlns:core_vm="using:GSS2.UI.Core.ViewModels"
xmlns:local_vm="using:GSS2.Test.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:DataType="local_vm:MainViewModel">
<Grid ColumnDefinitions="*,100" RowDefinitions="*,*">
<core:SectionPanel
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
CanScrollBackward="{Binding CanScrollBackward}"
CanScrollForward="{Binding CanScrollForward}"
ClipToBounds="True"
CurrentIndex="{Binding CurrentIndex}">
<Grid ColumnDefinitions="300,*">
<ContentControl
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding IlluminatorControllerViewModel}" />
<ContentControl
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding CameraViewModel}" />
</Grid>
<Grid ColumnDefinitions="100,*">
<Button
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{Binding ButtonCaptureClick}" />
<Grid
Grid.Column="1"
ColumnDefinitions="*,*"
RowDefinitions="*,*">
<core:OpenCvImage
Grid.Row="0"
Grid.Column="0"
Image="{Binding Image1}" />
<core:OpenCvImage
Grid.Row="0"
Grid.Column="1"
Image="{Binding Image2}" />
<core:OpenCvImage
Grid.Row="1"
Grid.Column="0"
Image="{Binding Image3}" />
<core:OpenCvImage
Grid.Row="1"
Grid.Column="1"
Image="{Binding Image4}" />
</Grid>
</Grid>
<ContentControl
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding ResourcesViewModel}" />
</core:SectionPanel>
<Button
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{Binding ButtonUpClick}"
IsEnabled="{Binding CanScrollBackward}" />
<Button
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Command="{Binding ButtonDownClick}"
IsEnabled="{Binding CanScrollForward}" />
</Grid>
</UserControl>
-15
View File
@@ -1,15 +0,0 @@
using Avalonia.Controls;
namespace GSS2.Test.Views;
public partial class MainView : UserControl
{
private readonly ILogger<MainView> _logger;
public MainView(ILogger<MainView> logger)
{
_logger = logger;
InitializeComponent();
_logger.LogInformation("{} initialized", nameof(MainView));
}
}
-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="GSS2.Test.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
-82
View File
@@ -1,82 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Migrations": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"GSS2.Core.Hardware.LightsService": "Information",
"GSS2.Core.Hardware.IlluminatorService": "Information",
"GSS2.Core.Hardware.CameraService": "Information",
"LibCamera": "Information",
"LibCamera-Camera": "Information",
"LibCamera-RPI": "Information",
"LibCamera-DeviceEnumerator": "Information",
"LibCamera-IPAProxy": "Information",
"LibCamera-V4L2": "Information",
"LibCamera-RPISTREAM": "Information",
"LibCamera-WRAPPER": "Information",
"Avalonia.*": "Warning"
}
},
"ConnectionStrings": {
"AmineContentCalibrationDatabasePath": "AmineContentCalibration.db",
"AmineContentResultsDatabasePath": "AmineContentResults.db"
},
"Hardware": {
"Illuminator": {
"WhitePwmPin": 13,
"Uv254PwmPin": 18,
"Uv365PwmPin": 12,
"WhiteRelayPin": 16,
"Uv254RelayPin": 15,
"Uv365RelayPin": 20,
"FanPin": 21,
"PwmFrequency": 200,
"MaxWhitePwmDuty": 1.0,
"MaxUv254PwmDuty": 1.0,
"MaxUv365PwmDuty": 1.0
},
"Camera": {
"CameraId": "/base/axi/pcie@1000120000/rp1/i2c@70000/imx477@1a",
"ViewFinderFrameBufferCount": 5,
"ViewFinderWidth": 4056,
"ViewFinderHeight": 3040,
"ViewFinderFps": 30,
"ImageCaptureFrameBufferCount": 5,
"ImageCaptureWidth": 4056,
"ImageCaptureHeight": 3040,
"CameraHardSettings": {
"AeEnable": false,
"ExposureValue": 8.0,
"ExposureTime": 30000,
"ExposureTimeMode": "ExposureTimeModeManual",
"AnalogueGain": 23.0,
"AnalogueGainMode": "AnalogueGainModeManual",
"Brightness": 0.0,
"Contrast": 1.0,
"AwbEnable": false,
"ColourGains": [
3.2,
1.0
],
"HdrMode": "HdrModeOff",
"NoiseReductionMode": "NoiseReductionModeOff",
"StatsOutputEnable": true,
"SyncMode": "SyncModeOff",
"CnnEnableInputTensor": false
},
"DecodeGainR": 3.2,
"DecodeGainG": 1.0,
"DecodeGainB": 1.0,
"DecodeBlackLevel": 4096
},
"TemperatureHumidity": {
"Bus": 6,
"Address": 69
},
"Lights": {
"ServerAddress": "http://127.0.0.1:50051"
}
}
}