feat: mock run mode

extract IImageCapturerService interface from ImageCapturerService
add ImageCapturerMockService as implementation of IImageCapturerService
add --mock flag in command line interface and implement its usage
This commit is contained in:
2026-06-17 10:35:36 +03:00
parent 80856ba60d
commit 913805eb70
5 changed files with 107 additions and 25 deletions
@@ -0,0 +1,10 @@
using OpenCvSharp;
namespace GSS2.Core.Analysis.AmineContent;
public interface IImageCapturerService
{
public Task CaptureImages(Action<(int i, double visible, double uv365, double uv254, Mat image)> callback, CancellationToken cancellationToken);
public Task CaptureImages(Func<(int i, double visible, double uv365, double uv254, Mat image), Task> callback, CancellationToken cancellationToken);
}
@@ -0,0 +1,57 @@
using System.Collections.ObjectModel;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using GSS2.Core.Extensions;
namespace GSS2.Core.Analysis.AmineContent;
public class ImageCapturerMockService : IImageCapturerService
{
// Список интенсивностей освещения для каждого кадра
public static readonly ReadOnlyCollection<(double visible, double uv365, double uv254)> IlluminatorIntensities =
[
(0.02500, 0.0, 0.0),
(0.01250, 0.0, 0.0),
(0.00625, 0.0, 0.0),
(0.00000, 1.0, 0.0),
(0.00000, 0.0, 1.0),
(0.00000, 1.0, 1.0),
(0.01250, 1.0, 0.0),
(0.01250, 0.0, 1.0),
(0.01250, 1.0, 1.0),
(0.00625, 1.0, 0.0),
(0.00625, 0.0, 1.0),
(0.00625, 1.0, 1.0)
];
private readonly string _imagesDirectoryPath;
private readonly ILogger<ImageCapturerMockService> _logger;
public ImageCapturerMockService(string imagesDirectoryPath, ILogger<ImageCapturerMockService> logger)
{
_imagesDirectoryPath = imagesDirectoryPath;
_logger = logger;
}
public async Task CaptureImages(Action<(int i, double visible, double uv365, double uv254, Mat image)> callback, CancellationToken cancellationToken) =>
await CaptureImages(async (data) => callback(data), cancellationToken);
public async Task CaptureImages(Func<(int i, double visible, double uv365, double uv254, Mat image), Task> callback, CancellationToken cancellationToken)
{
foreach (var (i, (visible, uv365, uv254)) in IlluminatorIntensities.Enumerate())
{
cancellationToken.ThrowIfCancellationRequested();
_logger.LogInformation("Capturing images: {} / {} ({};{};{})", i + 1, IlluminatorIntensities.Count(), visible, uv365, uv254);
Mat? image = Cv2.ImRead(Path.Join(_imagesDirectoryPath, $"{visible}-{uv365}-{uv254}.png"));
if (image is null)
throw new Exception("Image capture failed");
await callback.Invoke((i, visible, uv365, uv254, image));
}
}
}
@@ -11,7 +11,7 @@ using OpenCvSharp;
namespace GSS2.Core.Analysis.AmineContent; namespace GSS2.Core.Analysis.AmineContent;
public class ImageCapturerService public class ImageCapturerService : IImageCapturerService
{ {
// Список интенсивностей освещения для каждого кадра // Список интенсивностей освещения для каждого кадра
public static readonly ReadOnlyCollection<(double visible, double uv365, double uv254)> IlluminatorIntensities = public static readonly ReadOnlyCollection<(double visible, double uv365, double uv254)> IlluminatorIntensities =
+14 -4
View File
@@ -7,8 +7,7 @@ using Microsoft.Extensions.Logging.Console;
using GSS2.Core.Logging; using GSS2.Core.Logging;
using GSS2.Core.Hardware; using GSS2.Core.Hardware;
using GSS2.Core.Analysis.AmineContent.Database.Calibration; using GSS2.Core.Analysis.AmineContent.Database;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using GSS2.Core.Analysis.AmineContent; using GSS2.Core.Analysis.AmineContent;
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter; using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
@@ -128,8 +127,19 @@ public static class DependencyInjectionHelper
ServiceLifetime.Singleton ServiceLifetime.Singleton
); );
public static IServiceCollection AddAmineContentImageCapturerService(this IServiceCollection services) => services public static IServiceCollection AddAmineContentImageCapturerService(this IServiceCollection services, bool mock = false)
.AddSingleton<ImageCapturerService>(); {
if (!mock)
return services.AddSingleton<ImageCapturerService>();
return services.AddSingleton<ImageCapturerMockService>(serviceProvider =>
{
var logger = serviceProvider.GetService<ILogger<ImageCapturerMockService>>();
if (logger is null)
throw new Exception("Cannot get logger");
return new ImageCapturerMockService("./", logger);
});
}
public static IServiceCollection AddAmineContentAnalyzerService(this IServiceCollection services) => services public static IServiceCollection AddAmineContentAnalyzerService(this IServiceCollection services) => services
.AddSingleton<AnalyzerService>(); .AddSingleton<AnalyzerService>();
+25 -20
View File
@@ -10,9 +10,7 @@ using Avalonia.OpenGL.Egl;
using GSS2.Core; using GSS2.Core;
using GSS2.Core.Logging; using GSS2.Core.Logging;
using GSS2.Core.Analysis.AmineContent.Database.Calibration; using GSS2.Core.Analysis.AmineContent.Database;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using GSS2.Core.Hardware;
namespace GSS2.Commands; namespace GSS2.Commands;
@@ -29,6 +27,7 @@ public static class UI
public static readonly Option<bool> Fullscreen; public static readonly Option<bool> Fullscreen;
public static readonly Option<RenderingModes> RenderingMode; public static readonly Option<RenderingModes> RenderingMode;
public static readonly Option<bool> HideConsole; public static readonly Option<bool> HideConsole;
public static readonly Option<bool> Mock;
static UI() static UI()
{ {
@@ -52,6 +51,11 @@ public static class UI
Description = "Отключить вывод в консоль", Description = "Отключить вывод в консоль",
DefaultValueFactory = (result) => result.GetValue(RenderingMode) == RenderingModes.DRM DefaultValueFactory = (result) => result.GetValue(RenderingMode) == RenderingModes.DRM
}; };
Mock = new("--mock")
{
Description = "Запуск в фиктивном режиме без инициализации и использования периферии",
DefaultValueFactory = (result) => false
};
// Изменяем описания стандартных опций // Изменяем описания стандартных опций
Helper.TranslateDefaultOptionDescriptions(RootCommand); Helper.TranslateDefaultOptionDescriptions(RootCommand);
@@ -60,11 +64,13 @@ public static class UI
RootCommand.Add(Fullscreen); RootCommand.Add(Fullscreen);
RootCommand.Add(RenderingMode); RootCommand.Add(RenderingMode);
RootCommand.Add(HideConsole); RootCommand.Add(HideConsole);
RootCommand.Add(Mock);
RootCommand.SetAction(CommandAction); RootCommand.SetAction(CommandAction);
Command.Add(Fullscreen); Command.Add(Fullscreen);
Command.Add(RenderingMode); Command.Add(RenderingMode);
Command.Add(HideConsole); Command.Add(HideConsole);
Command.Add(Mock);
Command.SetAction(CommandAction); Command.SetAction(CommandAction);
} }
@@ -85,6 +91,7 @@ public static class UI
var fullscreen = result.GetValue(Fullscreen); var fullscreen = result.GetValue(Fullscreen);
var renderingMode = result.GetValue(RenderingMode); var renderingMode = result.GetValue(RenderingMode);
var hideConsole = result.GetValue(HideConsole); var hideConsole = result.GetValue(HideConsole);
var mock = result.GetValue(Mock);
// Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью, // Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью,
// поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом // поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом
@@ -92,7 +99,7 @@ public static class UI
SilenceConsole(); SilenceConsole();
// Собираем хост // Собираем хост
var host = BuildHost(); var host = BuildHost(mock);
// Запускаем хост асинхронно без блокировки потока // Запускаем хост асинхронно без блокировки потока
_ = host.RunAsync(); _ = host.RunAsync();
@@ -137,7 +144,7 @@ public static class UI
thread.Start(); thread.Start();
} }
private static IHost BuildHost() private static IHost BuildHost(bool mock)
{ {
// Создаём хост // Создаём хост
var builder = Host.CreateApplicationBuilder(); var builder = Host.CreateApplicationBuilder();
@@ -150,17 +157,19 @@ public static class UI
builder.Services.AddSingleton<LibCameraLogSink>(); builder.Services.AddSingleton<LibCameraLogSink>();
// Добавляем сервисы баз данных // Добавляем сервисы баз данных
builder.Services.AddAmineContentCalibrationContext(builder.Configuration); builder.Services.AddAmineContentContext(builder.Configuration);
builder.Services.AddAmineContentResultsContext(builder.Configuration);
// Добавляем сервисы аппаратной части и прочее // Добавляем сервисы аппаратной части и прочее
builder.Services.AddLightsService("Hardware:Lights"); if (!mock)
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity"); {
builder.Services.AddIlluminatorService("Hardware:Illuminator"); builder.Services.AddLightsService("Hardware:Lights");
builder.Services.AddCameraService("Hardware:Camera", true); builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
builder.Services.AddComputeResourcesService(); builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddComputeResourcesService();
}
builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images")); builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images"));
builder.Services.AddAmineContentImageCapturerService(); builder.Services.AddAmineContentImageCapturerService(mock);
builder.Services.AddAmineContentAnalyzerService(); builder.Services.AddAmineContentAnalyzerService();
// Добавляем элементы интерфейса как сервисы // Добавляем элементы интерфейса как сервисы
@@ -171,17 +180,13 @@ public static class UI
// Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост, // Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост,
// не будет создан ни одним из других сервисов, его необходимо создать превентивно // не будет создан ни одним из других сервисов, его необходимо создать превентивно
host.Services.GetRequiredService<LibCameraLogSink>(); if (!mock)
host.Services.GetRequiredService<LibCameraLogSink>();
// Применяем миграции баз данных если их схема данных изменялась // Применяем миграции баз данных если их схема данных изменялась
using (var scope = host.Services.CreateScope()) using (var scope = host.Services.CreateScope())
{ {
var context = scope.ServiceProvider.GetRequiredService<CalibrationContext>(); var context = scope.ServiceProvider.GetRequiredService<Context>();
context.Database.Migrate();
}
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ResultsContext>();
context.Database.Migrate(); context.Database.Migrate();
} }