feat: quality property

This commit is contained in:
2026-06-08 09:47:52 +03:00
parent 226d35f5d5
commit 0849a0ae67
7 changed files with 267 additions and 5 deletions
@@ -0,0 +1,131 @@
// <auto-generated />
using System;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
{
[DbContext(typeof(ResultsContext))]
[Migration("20260608053836_Results1")]
partial class Results1
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Results.ResultRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasComment("Уникальный идентификатор");
b.Property<string>("Image")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Изображение");
b.Property<string>("MaskImage")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Маска");
b.Property<double>("MaxValue")
.HasColumnType("REAL")
.HasComment("Максимальное значение");
b.Property<double?>("MeanQuality")
.HasColumnType("REAL")
.HasComment("Среднее качество обработки");
b.Property<double>("MeanValue")
.HasColumnType("REAL")
.HasComment("Среднее значение");
b.PrimitiveCollection<string>("MeanValues")
.IsRequired()
.HasColumnType("TEXT")
.HasComment("Средние значения");
b.Property<double>("MeanVariance")
.HasColumnType("REAL")
.HasComment("Средняя дисперсия");
b.Property<double>("MinValue")
.HasColumnType("REAL")
.HasComment("Минимальное значение");
b.Property<int>("ParticlesCount")
.HasColumnType("INTEGER")
.HasComment("Количество частиц");
b.PrimitiveCollection<string>("Qualities")
.HasColumnType("TEXT")
.HasComment("Качества обработки");
b.Property<string>("ResultImage")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Изображение результата");
b.Property<string>("SampleBrand")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Марка пробы");
b.Property<string>("SampleComment")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT")
.HasComment("Дополнительная информация о пробе");
b.Property<string>("SampleName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Название пробы");
b.Property<DateTime>("SamplingDateTime")
.HasColumnType("TEXT")
.HasComment("Дата и время отбора пробы");
b.Property<string>("SamplingPlace")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Место отбора пробы");
b.Property<string>("SeparatorType")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT")
.HasComment("Тип сепаратора");
b.Property<double>("VarianceOfMean")
.HasColumnType("REAL")
.HasComment("Дисперсия среднего");
b.PrimitiveCollection<string>("Variances")
.IsRequired()
.HasColumnType("TEXT")
.HasComment("Дисперсии");
b.HasKey("Id");
b.ToTable("ResultRecords");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
{
/// <inheritdoc />
public partial class Results1 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "MeanQuality",
table: "ResultRecords",
type: "REAL",
nullable: true,
comment: "Среднее качество обработки");
migrationBuilder.AddColumn<string>(
name: "Qualities",
table: "ResultRecords",
type: "TEXT",
nullable: true,
comment: "Качества обработки");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "MeanQuality",
table: "ResultRecords");
migrationBuilder.DropColumn(
name: "Qualities",
table: "ResultRecords");
}
}
}
@@ -40,6 +40,10 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
.HasColumnType("REAL")
.HasComment("Максимальное значение");
b.Property<double?>("MeanQuality")
.HasColumnType("REAL")
.HasComment("Среднее качество обработки");
b.Property<double>("MeanValue")
.HasColumnType("REAL")
.HasComment("Среднее значение");
@@ -61,6 +65,10 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
.HasColumnType("INTEGER")
.HasComment("Количество частиц");
b.PrimitiveCollection<string>("Qualities")
.HasColumnType("TEXT")
.HasComment("Качества обработки");
b.Property<string>("ResultImage")
.IsRequired()
.HasMaxLength(256)
@@ -46,8 +46,12 @@ public class ResultRecord
public required double MeanVariance { get; set; }
[Comment("Дисперсия среднего")]
public required double VarianceOfMean { get; set; }
[Comment("Среднее качество обработки")]
public required double? MeanQuality { get; set; }
[Comment("Средние значения")]
public required double[] MeanValues { get; set; }
[Comment("Дисперсии")]
public required double[] Variances { get; set; }
[Comment("Качества обработки")]
public required double[]? Qualities { get; set; }
}
@@ -21,11 +21,16 @@ using LiveChartsCore.SkiaSharpView.Painting;
using SkiaSharp;
using LiveChartsCore.SkiaSharpView.Painting.Effects;
using GSS2.UI.Core.Services;
using GSS2.Core.Hardware;
namespace GSS2.ViewModels.AmineContent;
public partial class AnalysisViewModel : ViewModelBase
{
const double QUALITY_MIN_MEAN = 2.0;
const double QUALITY_ALPHA = 0.7;
const double QUALITY_BETA = 0.3;
public event EventHandler? UpdateCharts;
private readonly ILogger<AnalysisViewModel> _logger;
@@ -35,6 +40,7 @@ public partial class AnalysisViewModel : ViewModelBase
private readonly AnalyzerService _analyzer;
private readonly DependencyInjectionViewLocator _viewLocator;
private readonly NavigationService _navigation;
private readonly LightsService _lights;
private CancellationTokenSource? _captureImagesTaskCts = null;
@@ -81,6 +87,10 @@ public partial class AnalysisViewModel : ViewModelBase
[ObservableProperty] public partial Axis[] VariancesXAxis { get; set; } = [new Axis()];
[ObservableProperty] public partial Axis[] VariancesYAxis { get; set; } = [new Axis()];
[ObservableProperty] public partial ISeries[] QualitiesHistogram { get; set; } = [];
[ObservableProperty] public partial Axis[] QualitiesXAxis { get; set; } = [new Axis()];
[ObservableProperty] public partial Axis[] QualitiesYAxis { get; set; } = [new Axis()];
public AnalysisViewModel(
ILogger<AnalysisViewModel> logger,
ResultsContext context,
@@ -88,7 +98,8 @@ public partial class AnalysisViewModel : ViewModelBase
ImageStorageService imageStorage,
AnalyzerService analyzer,
DependencyInjectionViewLocator viewLocator,
NavigationService navigation
NavigationService navigation,
LightsService lights
)
{
_logger = logger;
@@ -98,6 +109,7 @@ public partial class AnalysisViewModel : ViewModelBase
_analyzer = analyzer;
_viewLocator = viewLocator;
_navigation = navigation;
_lights = lights;
_logger.LogInformation("Инициализация");
// Получаем записи из базы данных
@@ -125,7 +137,9 @@ public partial class AnalysisViewModel : ViewModelBase
MeanVariance = -1,
VarianceOfMean = -1,
MeanValues = [],
Variances = []
Variances = [],
Qualities = [],
MeanQuality = -1
};
_logger.LogInformation("Инициализировано");
@@ -159,7 +173,9 @@ public partial class AnalysisViewModel : ViewModelBase
MeanVariance = -1,
VarianceOfMean = -1,
MeanValues = [],
Variances = []
Variances = [],
Qualities = [],
MeanQuality = -1
};
}
else
@@ -224,6 +240,13 @@ public partial class AnalysisViewModel : ViewModelBase
leftText: "Количество частиц",
bottomText: "Дисперсия"
);
(QualitiesHistogram, QualitiesXAxis, QualitiesYAxis) = BuildHistogram(
value.Qualities ?? [],
0,
100,
leftText: "Количество частиц",
bottomText: "Качество обработки"
);
}
catch (Exception ex)
{
@@ -338,6 +361,8 @@ public partial class AnalysisViewModel : ViewModelBase
SelectedRecord.MeanValue = EditingRecord.MeanValue;
SelectedRecord.MeanVariance = EditingRecord.MeanVariance;
SelectedRecord.VarianceOfMean = EditingRecord.VarianceOfMean;
SelectedRecord.Qualities = EditingRecord.Qualities;
SelectedRecord.MeanQuality = EditingRecord.MeanQuality;
// Если записи нет в базе данных
if (_context.Entry(SelectedRecord).State is EntityState.Detached)
@@ -496,6 +521,7 @@ public partial class AnalysisViewModel : ViewModelBase
// Вычисляем среднее и дисперсию каждого контура
var means = new List<double>();
var variances = new List<double>();
var qualities = new List<double>();
// Создаём временную маску для каждого контура
var contourMask = new OpenCvSharp.Mat(
@@ -520,6 +546,7 @@ public partial class AnalysisViewModel : ViewModelBase
// Добавляем значения в лист
means.Add(mean.Val0);
variances.Add(Math.Pow(stdDev.Val0, 2));
qualities.Add(CalculateQuality(mean.Val0, Math.Pow(stdDev.Val0, 2)));
}
contourMask.Release();
@@ -581,21 +608,48 @@ public partial class AnalysisViewModel : ViewModelBase
EditingRecord.VarianceOfMean = varianceOfMean;
EditingRecord.MeanValues = means.ToArray();
EditingRecord.Variances = variances.ToArray();
EditingRecord.MeanQuality = qualities.Average();
EditingRecord.Qualities = qualities.ToArray();
// Обновляем данные в редактируемой записи
// Так же Dispatcher.UIThread.Invoke нужен потому-что этот метод может (и скорее всего будет) вызван в другом потоке,
// а изменения данных в интерфейсе могут быть вызваны только из потока в котором интерфейс был создан
_ = Dispatcher.UIThread.InvokeAsync(() => EditingRecord = CopyRecord(EditingRecord));
// Если ошибок не было, то мигаем светодиодами зелёным
_ = Task.Run(async () =>
{
await Task.Delay(1000);
_lights.Flash(1, 1, System.Drawing.Color.Green);
await Task.Delay(3000);
_lights.Disconnect();
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Исключение во время анализа");
// Если была ошибка, то мигаем светодиодами красным
_ = Task.Run(async () =>
{
await Task.Delay(1000);
_lights.Flash(1, 1, System.Drawing.Color.Red);
await Task.Delay(3000);
_lights.Disconnect();
});
}
}
else
{
_logger.LogError("Ошибка при выполнении анализа");
// Если была ошибка, то мигаем светодиодами красным
_ = Task.Run(async () =>
{
await Task.Delay(1000);
_lights.Flash(1, 1, System.Drawing.Color.Red);
await Task.Delay(3000);
_lights.Disconnect();
});
}
}, cancellationToken);
@@ -666,7 +720,15 @@ public partial class AnalysisViewModel : ViewModelBase
MeanVariance = source.MeanVariance,
VarianceOfMean = source.VarianceOfMean,
MeanValues = source.MeanValues,
Variances = source.Variances
Variances = source.Variances,
Qualities = source.Qualities ??
Enumerable.Zip(source.MeanValues, source.Variances)
.Select((pair) => CalculateQuality(pair.First, pair.Second))
.ToArray(),
MeanQuality = source.MeanQuality ??
Enumerable.Zip(source.MeanValues, source.Variances)
.Select((pair) => CalculateQuality(pair.First, pair.Second))
.Average(),
};
}
@@ -785,4 +847,7 @@ public partial class AnalysisViewModel : ViewModelBase
}
private static (ISeries[] Series, Axis[] XAxes, Axis[] YAxes) BuildHistogram(IEnumerable<double> values, double? min = null, double? max = null, string leftText = "", string rightText = "", string bottomText = "", string barLegend = "", string lineLegend = "") =>
BuildHistogram(values.Select(v => (float)v), min, max, leftText, rightText, bottomText, barLegend, lineLegend);
private static double CalculateQuality(double mean, double variance) =>
100.0 * Math.Exp(-1 * (QUALITY_ALPHA * Math.Max(0, QUALITY_MIN_MEAN - mean) + QUALITY_BETA * Math.Pow(variance, 2)));
}
+14 -1
View File
@@ -192,6 +192,8 @@
<RowDefinition Height="Auto" /> <!-- Количество частиц (поле) / Среднее значение (поле) -->
<RowDefinition Height="Auto" /> <!-- Средняя дисперсия (подпись) / Дисперсия среднего (подпись) -->
<RowDefinition Height="Auto" /> <!-- Средняя дисперсия (поле) / Дисперсия среднего (поле)-->
<RowDefinition Height="Auto" /> <!-- Среднее качество (подпись) -->
<RowDefinition Height="Auto" /> <!-- Среднее качество (поле) -->
<RowDefinition Height="*" /> <!-- Пустое пространство -->
</Grid.RowDefinitions>
@@ -233,7 +235,10 @@
<TextBlock Grid.Row="6" Grid.Column="1" Classes="mini" Text="Дисперсия среднего:" />
<!-- Дисперсия среднего (поле) -->
<TextBox Grid.Row="7" Grid.Column="1" Classes="singleline" Text="{Binding EditingRecord.VarianceOfMean, StringFormat={}{0:N4}}" PlaceholderText="Дисперсия среднего:" IsReadOnly="True" />
<!-- Среднее качество (подпись) -->
<TextBlock Grid.Row="8" Grid.Column="0" Classes="mini" Text="Среднее качество обработки:" Grid.ColumnSpan="2" />
<!-- Среднее качество (поле) -->
<TextBox Grid.Row="9" Grid.Column="0" Classes="singleline" Text="{Binding EditingRecord.MeanQuality, StringFormat={}{0:N4}}" PlaceholderText="Среднее качество обработки:" IsReadOnly="True" Grid.ColumnSpan="2" />
</Grid>
<!-- Изображения -->
@@ -344,6 +349,14 @@
</Grid>
<!-- Гистограмма качества обработки -->
<lvc:CartesianChart x:Name="QualitiesChart"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
LegendTextPaint="{Binding LegendTextPaint, Mode=OneWay}"
Series="{Binding QualitiesHistogram, Mode=OneWay}"
XAxes="{Binding QualitiesXAxis, Mode=OneWay}"
YAxes="{Binding QualitiesYAxis, Mode=OneWay}" />
<!-- Гистограмма средних значений -->
<lvc:CartesianChart x:Name="MeanValuesChart"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
@@ -18,5 +18,6 @@ public partial class AnalysisView : UserControl
{
MeanValuesChart.CoreChart.Update();
VariancesChart.CoreChart.Update();
QualitiesChart.CoreChart.Update();
}
}