feat: massive rework

1) GSS2.Core:
- strip prefixes in GSS2.Core.Analysis.AmineContent namespace class names
- add IEnumerable<double>.Variance extension
- remake analysis + update database so not needed to store all every file, calibration data also stored in database, also currently capturing images stored in system temporary directory and rewriting every time
2) GSS.UI.Core: AsyncImageRecordViewModel make zoom and pans public
3) GSS:
- remove image-storage-clear command
- remove ResultsView and ResultsViewModel, results presented in analysis
- rework calibration views
- add analysis views
- add text and number fields editors view
- add confirmation view on danger buttons
This commit is contained in:
2026-05-13 12:56:32 +03:00
parent ec95eef375
commit 3cc4d21947
63 changed files with 4359 additions and 3961 deletions
@@ -0,0 +1,123 @@
using System.Collections.ObjectModel;
using System.Drawing;
using GSS2.Core.Extensions;
using GSS2.Core.Hardware;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
namespace GSS2.Core.Analysis.AmineContent;
public class ImageCapturerService
{
// Список интенсивностей освещения для каждого кадра
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 ILogger<ImageCapturerService> _logger;
private readonly CameraService _camera;
private readonly IlluminatorService _illuminator;
private readonly LightsService _lights;
public ImageCapturerService(
ILogger<ImageCapturerService> logger,
CameraService camera,
IlluminatorService illuminator,
LightsService lights
)
{
_logger = logger;
_camera = camera;
_illuminator = illuminator;
_lights = lights;
}
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)
{
// Включаем осветитель
_illuminator.TurnOn();
// Если ViewFinder запущен, то для съёмки изображений его нужно остановить
// потом после съёмки снова запустить
var needRestartViewFinder = _camera.ViewFinderStarted;
if (needRestartViewFinder)
_camera.StopViewFinder();
try
{
// Для каждой интенсивности
foreach (var (i, (visible, uv365, uv254)) in IlluminatorIntensities.Enumerate())
{
// Выбрасываем исключение если задача отменена
cancellationToken.ThrowIfCancellationRequested();
// Логгируем прогресс
_logger.LogInformation("Capturing images: {} / {} ({};{};{})", i + 1, IlluminatorIntensities.Count(), visible, uv365, uv254);
// Отображаем прогресс на светодиодах
_lights.Load(0.3, 1, (double)(i + 1) / IlluminatorIntensities.Count * 100, (double)i / IlluminatorIntensities.Count * 100, Color.Yellow);
// Устанавливаем интенсивность
_illuminator.SetIntensity(visible, uv365, uv254);
// Получаем изображение
Mat? image;
image = await _camera.CaptureImage(cancellationToken, 2);
if (image is null)
throw new Exception("Image capture failed");
// Вызываем коллбек
await callback.Invoke((i, visible, uv365, uv254, image));
}
// Если ошибок не было, то мигаем светодиодами зелёным
_ = Task.Run(async () =>
{
await Task.Delay(1000);
_lights.Flash(1, 1, Color.Green);
await Task.Delay(3000);
_lights.Disconnect();
});
}
catch (OperationCanceledException)
{ }
catch
{
// Если была ошибка, то мигаем светодиодами красным
_ = Task.Run(async () =>
{
await Task.Delay(1000);
_lights.Flash(1, 1, Color.Red);
await Task.Delay(3000);
_lights.Disconnect();
});
throw;
}
finally
{
// В любом случае отключаем осветитель
_illuminator.TurnOff();
// Запускаем обратно ViewFinder, если он был запущен и ещё не запущен обратно
if (needRestartViewFinder && !_camera.ViewFinderStarted)
_camera.StartViewFinder();
}
}
}