forked from amkovkov/GranuSightSoftware2
feat: SeparatorCalibration View and ViewModel
This commit is contained in:
@@ -775,7 +775,7 @@ public class AmineContentAnalyzerService
|
||||
p.First.VisibleIntensity != p.Second.VisibleIntensity ||
|
||||
p.First.Uv365Intensity != p.Second.Uv365Intensity ||
|
||||
p.First.Uv254Intensity != p.Second.Uv254Intensity))
|
||||
throw new Exception("Image records indencities to correlate with separator images intencities");
|
||||
throw new Exception("Image records intensities to correlate with separator images intensities");
|
||||
|
||||
var imagePaths = GetImageRecordsFilePaths(imageRecords);
|
||||
var roi = CalculateSeparatorRoi(imagePaths, 5, cancellationToken);
|
||||
@@ -867,7 +867,7 @@ public class AmineContentAnalyzerService
|
||||
return calibrationRecord;
|
||||
}
|
||||
|
||||
public async Task<(Mat result, Mat mask)> CalcaulateAmineContent(IEnumerable<ImageRecord> sampleImageRecords, IEnumerable<ImageRecord> separatorImageRecords, CalibrationRecord calibrationRecord, CancellationToken cancellationToken = default)
|
||||
public async Task<(Mat result, Mat mask)> CalculateAmineContent(IEnumerable<ImageRecord> sampleImageRecords, IEnumerable<ImageRecord> separatorImageRecords, CalibrationRecord calibrationRecord, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var sampleImagePaths = GetImageRecordsFilePaths(sampleImageRecords);
|
||||
@@ -986,10 +986,10 @@ public class AmineContentAnalyzerService
|
||||
}
|
||||
}
|
||||
|
||||
// Срезаем 99 проценталь (1% самых горячих пикселей), которые скорее всего являются шумом или загрязенением
|
||||
// Срезаем 99.9 процентиль (0.1% самых горячих пикселей), которые скорее всего являются шумом или загрязнением
|
||||
var oldMean = Cv2.Mean(result, sampleMask).Val0;
|
||||
values.Sort();
|
||||
float p99 = values[(int)(values.Length * 0.99)];
|
||||
float p99 = values[(int)(values.Length * 0.999)];
|
||||
Cv2.Threshold(result, result, p99, p99, ThresholdTypes.Trunc);
|
||||
var newMean = Cv2.Mean(result, sampleMask).Val0;
|
||||
var scale = oldMean / newMean;
|
||||
|
||||
@@ -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 AmineContentImageCapturerService
|
||||
{
|
||||
// Список интенсивностей освещения для каждого кадра
|
||||
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<AmineContentImageCapturerService> _logger;
|
||||
private readonly CameraService _camera;
|
||||
private readonly IlluminatorService _illuminator;
|
||||
private readonly LightsService _lights;
|
||||
|
||||
public AmineContentImageCapturerService(
|
||||
ILogger<AmineContentImageCapturerService> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,32 @@ using Microsoft.IdentityModel.Protocols.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
using GSS2.Core.Logging;
|
||||
using GSS2.Core.Hardware;
|
||||
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
using GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
using GSS2.Core.Analysis.AmineContent;
|
||||
|
||||
using ConsoleFormatter = GSS2.Core.Logging.ConsoleFormatter;
|
||||
|
||||
namespace GSS2.Core;
|
||||
|
||||
public static class DependencyInjectionHelper
|
||||
{
|
||||
public static IConfigurationBuilder AddAppSettingsJson(this IConfigurationBuilder configuration) =>
|
||||
configuration.AddJsonFile(Path.Join(Path.GetDirectoryName(Environment.ProcessPath), "appsettings.json"));
|
||||
|
||||
public static ILoggingBuilder ConfigureLogging(this ILoggingBuilder logging)
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddConsole(options => options.FormatterName = nameof(ConsoleFormatter));
|
||||
logging.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
|
||||
logging.AddProvider(new FileLoggerProvider("full.log", true));
|
||||
logging.AddProvider(new FileLoggerProvider("last_run.log", false));
|
||||
return logging;
|
||||
}
|
||||
public static IServiceCollection AddTemperatureHumidityService(this IServiceCollection services, string section) => services
|
||||
.AddSingleton(
|
||||
serviceProvider =>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace GSS2.Core.Extensions;
|
||||
|
||||
public static class MatExtensions
|
||||
{
|
||||
public static void ImWrite(this Mat mat, string path)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
if (directory is not null && !Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
mat.ImWrite(path);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ public class CameraService : IDisposable
|
||||
public bool IsImageCapturing { get; private set; } = false;
|
||||
public List<FrameBuffer> ViewFinderBufferQueue { get; private set; } = new List<FrameBuffer>();
|
||||
public CameraServiceConfiguration Configuration => _configuration;
|
||||
public bool ViewFinderStarted { get; private set; } = false;
|
||||
|
||||
public CameraService(ILogger<CameraService> logger, CameraServiceConfiguration? configuration = null, bool autoInitialize = false)
|
||||
{
|
||||
@@ -315,7 +316,7 @@ public class CameraService : IDisposable
|
||||
throw new Exception($"Error while camera start {result}");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Camera settings: \n{}", GetSettings(4));
|
||||
_logger.LogDebug("Camera settings: \n{}", GetSettings(4));
|
||||
|
||||
_state |= State.CameraStarted;
|
||||
}
|
||||
@@ -840,6 +841,8 @@ public class CameraService : IDisposable
|
||||
_viewFinderRequestCompletedHandler is not null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
ViewFinderStarted = true;
|
||||
|
||||
_viewFinderTimer = new System.Timers.Timer()
|
||||
{
|
||||
Interval = 1000 / _configuration.ViewFinderFps,
|
||||
@@ -919,9 +922,11 @@ public class CameraService : IDisposable
|
||||
}
|
||||
public void StopViewFinder()
|
||||
{
|
||||
ViewFinderStarted = false;
|
||||
_viewFinderTimer?.Stop();
|
||||
_viewFinderTimer?.Dispose();
|
||||
_viewFinderTimer = null;
|
||||
_viewFinderTimerElapsed = null;
|
||||
_camera?.RequestCompleted -= _viewFinderRequestCompletedHandler;
|
||||
_viewFinderRequestCompletedHandler = null;
|
||||
lock (ViewFinderBufferQueue)
|
||||
|
||||
Reference in New Issue
Block a user