forked from amkovkov/GranuSightSoftware2
feat: testing and fixes
This commit is contained in:
@@ -172,6 +172,7 @@ public partial class AnalyzerService
|
||||
}
|
||||
|
||||
var sampleDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.SelectMany(r => r.Features.Chunk(FEATURES_LENGTH))
|
||||
.Select(f => new ClusterizationData
|
||||
@@ -231,6 +232,7 @@ public partial class AnalyzerService
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet = sampleRecords
|
||||
.AsEnumerable()
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.Where(r => r.MeasuredContent >= 0 || r.MixtureActualRate >= 0)
|
||||
.SelectMany(r =>
|
||||
@@ -290,7 +292,7 @@ public partial class AnalyzerService
|
||||
model.Dispose();
|
||||
}
|
||||
|
||||
public async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, Mat result, string separatorType, string sampleBrand)?> Analyze(IEnumerable<ImageData> imagesData)
|
||||
public async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, Mat result)?> Analyze(IEnumerable<ImageData> imagesData)
|
||||
{
|
||||
if (!Initialized)
|
||||
return null;
|
||||
@@ -304,7 +306,7 @@ public partial class AnalyzerService
|
||||
if (segmentationResult is null)
|
||||
return null;
|
||||
|
||||
var (avgImage, roi, mask, contours, separatorType, sampleBrand) = segmentationResult.Value;
|
||||
var (avgImage, roi, mask, contours) = segmentationResult.Value;
|
||||
|
||||
var images = new List<(Mat mat, double visible, double uv365, double uv254)>();
|
||||
foreach (var imageData in imagesData
|
||||
@@ -410,7 +412,7 @@ public partial class AnalyzerService
|
||||
Cv2.Erode(mask, mask, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)));
|
||||
Cv2.FindContours(mask, out var newContours, out _, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
return (avgImage, roi, mask, contours, result, separatorType, sampleBrand);
|
||||
return (avgImage, roi, mask, contours, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -418,10 +420,10 @@ public partial class AnalyzerService
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, string separatorType, string sampleBrand)?> Segment(IEnumerable<ImageData> imagesData)
|
||||
private async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours)?> Segment(IEnumerable<ImageData> imagesData)
|
||||
{
|
||||
var separatorPrefix = "SEPARATOR";
|
||||
var samplePrefix = "SAMPLE";
|
||||
var separatorPrefix = "separator";
|
||||
var samplePrefix = "sample";
|
||||
|
||||
if (imagesData.Count() != IMAGES_COUNT)
|
||||
return null;
|
||||
@@ -522,24 +524,14 @@ public partial class AnalyzerService
|
||||
result = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_8UC1, new Scalar(0));
|
||||
Cv2.DrawContours(result, contours.Where(ContourIsValid), -1, new Scalar(255), -1);
|
||||
|
||||
_logger.LogInformation("Найдено гранул: {}", contours.Where(ContourIsValid).Count());
|
||||
_logger.LogInformation("Найдено гранул: {}", contours.Count(ContourIsValid));
|
||||
_logger.LogInformation("Детекторованные обекты:\n\t{}", string.Join("\n\t", frequency.Select(f => $"{f.Key}: {f.Value}")));
|
||||
|
||||
return (
|
||||
avgImage,
|
||||
roi.Value,
|
||||
result,
|
||||
contours.Where(ContourIsValid),
|
||||
frequency
|
||||
.Where(kv => kv.Key.StartsWith(separatorPrefix))
|
||||
.MaxBy(kv => kv.Value)
|
||||
.Key
|
||||
.Substring(separatorPrefix.Count() + 1),
|
||||
frequency
|
||||
.Where(kv => kv.Key.StartsWith(samplePrefix))
|
||||
.MaxBy(kv => kv.Value)
|
||||
.Key
|
||||
.Substring(samplePrefix.Count() + 1)
|
||||
contours.Where(ContourIsValid)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ public class CameraService : IDisposable
|
||||
private FrameBufferAllocator? _imageCaptureFrameBufferAllocator;
|
||||
private Stream? _imageCaptureStream;
|
||||
private List<MappedBuffer>? _imageCaptureMappedBuffers;
|
||||
private System.Timers.ElapsedEventHandler? _viewFinderTimerElapsed = null;
|
||||
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
|
||||
private readonly Dictionary<string, object> _cameraHardSettings = new Dictionary<string, object>();
|
||||
|
||||
@@ -238,7 +237,7 @@ public class CameraService : IDisposable
|
||||
_cameraConfiguration = _camera.GenerateConfiguration(StreamRoleEnum.Raw);
|
||||
|
||||
|
||||
_imageCaptureStreamConfiguration = _cameraConfiguration.Skip(1).First();
|
||||
_imageCaptureStreamConfiguration = _cameraConfiguration.First();
|
||||
_imageCaptureStreamConfiguration.BufferCount = checked((uint)_configuration.ImageCaptureFrameBufferCount);
|
||||
_imageCaptureStreamConfiguration.Size = new Size(
|
||||
checked((uint)_configuration.ImageCaptureWidth),
|
||||
|
||||
@@ -54,6 +54,12 @@ public class ZoomPanImage : Control
|
||||
get => GetValue(PanYProperty);
|
||||
set => SetValue(PanYProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<bool> CanUserZoomPanProperty = AvaloniaProperty.Register<ZoomPanImage, bool>(nameof(CanUserZoomPan), defaultValue: true);
|
||||
public bool CanUserZoomPan
|
||||
{
|
||||
get => GetValue(CanUserZoomPanProperty);
|
||||
set => SetValue(CanUserZoomPanProperty, value);
|
||||
}
|
||||
|
||||
public ZoomPanImage()
|
||||
: base()
|
||||
@@ -64,6 +70,10 @@ public class ZoomPanImage : Control
|
||||
PanX = 0f;
|
||||
PanY = 0f;
|
||||
};
|
||||
|
||||
AffectsRender<ZoomPanImage>(ZoomProperty);
|
||||
AffectsRender<ZoomPanImage>(PanXProperty);
|
||||
AffectsRender<ZoomPanImage>(PanYProperty);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
@@ -75,6 +85,8 @@ public class ZoomPanImage : Control
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs ea)
|
||||
{
|
||||
if (!CanUserZoomPan)
|
||||
return;
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
var position = ea.GetPosition(this);
|
||||
@@ -107,6 +119,8 @@ public class ZoomPanImage : Control
|
||||
}
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs ea)
|
||||
{
|
||||
if (!CanUserZoomPan)
|
||||
return;
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (ea.Properties.IsLeftButtonPressed)
|
||||
@@ -136,6 +150,8 @@ public class ZoomPanImage : Control
|
||||
}
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs ea)
|
||||
{
|
||||
if (!CanUserZoomPan)
|
||||
return;
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (!ea.Properties.IsLeftButtonPressed)
|
||||
@@ -151,6 +167,8 @@ public class ZoomPanImage : Control
|
||||
}
|
||||
protected override void OnPointerMoved(PointerEventArgs ea)
|
||||
{
|
||||
if (!CanUserZoomPan)
|
||||
return;
|
||||
if (ea.Pointer.Type == PointerType.Mouse)
|
||||
{
|
||||
if (_lastMousePosition is null || Zoom == 1) // Если кнопка не нажата или зум == 1, то ничего не делаем
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -16,6 +17,10 @@ using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.SkiaSharpView.Painting.Effects;
|
||||
|
||||
using UnitsNet;
|
||||
|
||||
using MathNet.Numerics.Statistics;
|
||||
|
||||
using GSS2.Core.Hardware;
|
||||
using GSS2.Core.Analysis.AmineContent;
|
||||
using GSS2.Core.Analysis.AmineContent.Database;
|
||||
@@ -24,10 +29,6 @@ using GSS2.Core.Extensions;
|
||||
using GSS2.UI.Core.ViewModels;
|
||||
using GSS2.UI.Core.Services;
|
||||
|
||||
using UnitsNet;
|
||||
|
||||
using MathNet.Numerics.Statistics;
|
||||
|
||||
namespace GSS2.ViewModels.AmineContent;
|
||||
|
||||
public partial class AnalysisViewModel : ViewModelBase
|
||||
@@ -85,8 +86,8 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
public partial class RecordViewModel : ViewModelBase
|
||||
{
|
||||
public const double UNPROCESSED_THRESHOLD = 0.5;
|
||||
public const double HOTSPOT_THRESHOLD = 1.2;
|
||||
public const double UNPROCESSED_THRESHOLD = 0.6;
|
||||
public const double HOTSPOT_THRESHOLD = 1.1;
|
||||
public const double EPSILON = 1e-10;
|
||||
public const double UNPROCESSED_FRACTION_THRESHOLD = 10;
|
||||
|
||||
@@ -415,19 +416,6 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial int ParticleIndex { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Представление <see cref="ResultRecord.ImageId"/> выбранной гранулы
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel ParticleImage { get; set; } = new();
|
||||
/// <summary>
|
||||
/// Представление <see cref="ResultRecord.MaskImageId"/> выбранной гранулы
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel ParticleMaskImage { get; set; } = new();
|
||||
/// <summary>
|
||||
/// Представление <see cref="ResultRecord.ResultImageId"/> выбранной гранулы
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial AsyncImageRecordViewModel ParticleResultImage { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Выбрано ли изображение <see cref="ParticleImage"/>
|
||||
/// </summary>
|
||||
@@ -452,7 +440,12 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
/// <summary>
|
||||
/// Смещение по Y изображений <see cref="ParticleImage"/>, <see cref="ParticleMaskImage"/> и <see cref="ParticleResultImage"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial float ImagesPanY { get; set; } = 0;
|
||||
[ObservableProperty] public partial float ParticleImagesPanY { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Ширина изображений <see cref="ParticleImage"/>, <see cref="ParticleMaskImage"/> и <see cref="ParticleResultImage"/>
|
||||
/// </summary>
|
||||
[ObservableProperty] public partial Rect ParticleImagesBounds { get; set; } = new Rect();
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Нормированная обработанная площадь выбранной гранулы
|
||||
@@ -501,6 +494,33 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
InnerStdDevs.Clear();
|
||||
InnerHeterogeneities.Clear();
|
||||
|
||||
if (Values.Count() == 0)
|
||||
{
|
||||
MeanProcessedArea = -1;
|
||||
ProcessedAreaP10 = -1;
|
||||
ProcessedAreaP50 = -1;
|
||||
ProcessedAreaP90 = -1;
|
||||
ProcessedAreaStdDev = -1;
|
||||
InterHeterogeneity = -1;
|
||||
ProcessedAreaDeltaP90P10 = -1;
|
||||
UnprocessedFraction = -1;
|
||||
MeanInnerHeterogeneity = -1;
|
||||
MeanInnerStdDev = -1;
|
||||
MeanHotspotArea = -1;
|
||||
MeanOpticalIntegral = -1;
|
||||
|
||||
ProcessedAreasHistogram.Clear();
|
||||
UnprocessedAreasHistogram.Clear();
|
||||
HotspotAreasHistogram.Clear();
|
||||
OpticalIntegralsHistogram.Clear();
|
||||
InnerStdDevsHistogram.Clear();
|
||||
InnerHeterogeneitiesHistogram.Clear();
|
||||
|
||||
ParticleIndex = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Для каждой гранулы
|
||||
var max = Values.Max(vs => vs.Max());
|
||||
foreach (var value in Values)
|
||||
@@ -517,10 +537,10 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
|
||||
// Вычисляем значения
|
||||
var area = normalized.Count();
|
||||
var processedArea = normalized.Count(v => v > 1) / area * 100;
|
||||
var unprocessedArea = normalized.Count(v => v <= 1) / area * 100;
|
||||
var hotspotArea = normalized.Count(v => v > 2) / area * 100;
|
||||
var opticalIntegral = normalized.Sum() / area * 100;
|
||||
var processedArea = normalized.Count(v => v > 1) * 100.0 / area;
|
||||
var unprocessedArea = normalized.Count(v => v <= 1) * 100.0 / area;
|
||||
var hotspotArea = normalized.Count(v => v > 2) * 100.0 / area;
|
||||
var opticalIntegral = normalized.Sum() * 100.0 / area;
|
||||
var innerStdDev = normalized.StdDev();
|
||||
var innerHeterogeneity = innerStdDev / (opticalIntegral + EPSILON);
|
||||
|
||||
@@ -558,6 +578,7 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
|
||||
// Сбрасываем индекс выбранной гранулы
|
||||
ParticleIndex = 0;
|
||||
ZoomPanOnParticle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -578,7 +599,7 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
{
|
||||
if (ParticlesCount == 0)
|
||||
return;
|
||||
ParticleIndex = (ParticleIndex - 1 + ParticlesCount) % ParticlesCount;
|
||||
// ParticleIndex = (ParticleIndex - 1 + ParticlesCount) % ParticlesCount;
|
||||
}
|
||||
/// <summary>
|
||||
/// Переключить на изображение <see cref="RecordViewModel.Image"/>
|
||||
@@ -638,8 +659,35 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
ParticleInnerHeterogeneity = InnerHeterogeneities[value];
|
||||
// Обновляем гистограмму
|
||||
ParticleValuesHistogram.Set(NormalizedValues[value], 0, 3, "Количество, шт", "Значение");
|
||||
// Обновляем изображение
|
||||
|
||||
ZoomPanOnParticle();
|
||||
}
|
||||
|
||||
partial void OnParticleImagesBoundsChanged(Rect value)
|
||||
{
|
||||
ZoomPanOnParticle();
|
||||
}
|
||||
|
||||
private void ZoomPanOnParticle()
|
||||
{
|
||||
if (ParticleIndex < 0 ||
|
||||
ParticleIndex >= Xs.Count() ||
|
||||
ParticleIndex >= Ys.Count())
|
||||
return;
|
||||
if (ParticleImagesBounds.Width == 0 ||
|
||||
ParticleImagesBounds.Height == 0)
|
||||
return;
|
||||
|
||||
var minX = Xs[ParticleIndex].Min();
|
||||
var maxX = Xs[ParticleIndex].Max();
|
||||
var minY = Ys[ParticleIndex].Min();
|
||||
var maxY = Ys[ParticleIndex].Max();
|
||||
|
||||
ParticleImagesZoom = MathF.Max((float)ParticleImagesBounds.Width, (float)ParticleImagesBounds.Height) * MathF.Max(maxX - minX, maxY - minY) / AnalyzerService.ANALYSIS_IMAGE_SIZE;
|
||||
ParticleImagesPanX = -((maxX + minX) / 2 / AnalyzerService.ANALYSIS_IMAGE_SIZE - 0.5f) * (float)ParticleImagesBounds.Width * ParticleImagesZoom;
|
||||
ParticleImagesPanY = -((maxY + minY) / 2 / AnalyzerService.ANALYSIS_IMAGE_SIZE - 0.5f) * (float)ParticleImagesBounds.Height * ParticleImagesZoom;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler? UpdateCharts;
|
||||
@@ -868,6 +916,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в списке {}", separatorRecord.Id, nameof(SeparatorComboBoxItems));
|
||||
return;
|
||||
}
|
||||
// Берём индекс изменённого элемента в выпадающем списке марок
|
||||
var editedComboBoxIndex = SeparatorComboBoxItems.IndexOf(editedComboBoxItem);
|
||||
// Если текущий индекс равен изменённому и это новая записть, то обновляем данные в записи
|
||||
if (SeparatorComboBoxSelectedIndex == editedComboBoxIndex && IsNewRecord)
|
||||
Record.SeparatorType = separatorRecord.Type;
|
||||
// Обновляем элемент из выпадающего списка марок
|
||||
editedComboBoxItem.Type = separatorRecord.Type;
|
||||
}
|
||||
@@ -911,6 +964,15 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в списке {}", brandRecord.Id, nameof(BrandComboBoxItems));
|
||||
return;
|
||||
}
|
||||
// Берём индекс изменённого элемента в выпадающем списке марок
|
||||
var editedComboBoxIndex = BrandComboBoxItems.IndexOf(editedComboBoxItem);
|
||||
// Если текущий индекс равен изменённому и это новая записть, то обновляем данные в записи
|
||||
if (BrandComboBoxSelectedIndex == editedComboBoxIndex && IsNewRecord)
|
||||
{
|
||||
Record.BrandName = brandRecord.BrandName;
|
||||
Record.MixtureName = brandRecord.MixtureName;
|
||||
Record.MixtureNormalRate = brandRecord.MixtureNormalRate;
|
||||
}
|
||||
// Обновляем элемент из выпадающего списка марок
|
||||
editedComboBoxItem.BrandName = brandRecord.BrandName;
|
||||
editedComboBoxItem.MixtureName = brandRecord.MixtureName;
|
||||
@@ -926,13 +988,16 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
else // Если записей нет,
|
||||
MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана
|
||||
|
||||
// Подписываемся на изменения свойств для обновления CanScroll
|
||||
// Подписываемся на изменения свойств для обновления CanInteract и обновления графиков
|
||||
PropertyChanged += (_, ea) =>
|
||||
{
|
||||
if (ea.PropertyName == nameof(MenuOpened) ||
|
||||
ea.PropertyName == nameof(MainSectionIsScrolling) ||
|
||||
ea.PropertyName == nameof(InfoSectionIsScrolling))
|
||||
UpdateCanInteract();
|
||||
if (ea.PropertyName == nameof(MenuSelectedIndex) ||
|
||||
ea.PropertyName == nameof(InfoSectionCurrentIndex) )
|
||||
UpdateCharts?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
|
||||
_logger.LogInformation("Инициализировано");
|
||||
@@ -946,81 +1011,79 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
// Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку
|
||||
if (value >= MenuItems.Count())
|
||||
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(MenuSelectedIndex), MenuSelectedIndex, nameof(MenuItems), MenuItems.Count());
|
||||
// Если значение больше или равно 0, то значит выбран один из элементов меню
|
||||
if (value >= 0 && value < MenuItems.Count())
|
||||
// Если значение меньше 0, то значит элемент меню не выбран
|
||||
if (value < 0 || value > MenuItems.Count())
|
||||
{
|
||||
_logger.LogTrace("Выбран элемент из списка: {}", value);
|
||||
// Находим запись в базе данных
|
||||
var selectedRecord = _context.ResultRecords.Find(MenuItems[value].Id);
|
||||
// Если запись найдена, то заполняем поля из базы данных
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
// Заполняем простые поля
|
||||
Record.Id = selectedRecord.Id;
|
||||
Record.Uuid = selectedRecord.Uuid;
|
||||
Record.CreateDateTime = selectedRecord.CreateDateTime;
|
||||
Record.EditDateTime = selectedRecord.EditDateTime;
|
||||
Record.SampleName = selectedRecord.SampleName;
|
||||
Record.SampleComment = selectedRecord.SampleComment;
|
||||
Record.SamplingDateTime = selectedRecord.SamplingDateTime;
|
||||
Record.SamplingPlace = selectedRecord.SamplingPlace;
|
||||
Record.SeparatorType = selectedRecord.SeparatorType;
|
||||
Record.BrandName = selectedRecord.BrandName;
|
||||
Record.MixtureName = selectedRecord.MixtureName;
|
||||
Record.MixtureNormalRate = selectedRecord.MixtureNormalRate;
|
||||
Record.ParticlesCount = selectedRecord.ParticlesCount;
|
||||
Record.Values = selectedRecord.ValueData.Chunk(selectedRecord.ValueCount).Select(v => v.ToArray()).ToList();
|
||||
Record.Xs = selectedRecord.XData.Chunk(selectedRecord.XCount).Select(v => v.ToArray()).ToList();
|
||||
Record.Ys = selectedRecord.YData.Chunk(selectedRecord.YCount).Select(v => v.ToArray()).ToList();
|
||||
// Логгируем как ошибку
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", MenuItems[value].Id, nameof(_context.BrandRecords));
|
||||
// Сохраняем несуществующий индекс
|
||||
var nonExistingIndex = MenuSelectedIndex;
|
||||
// Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
|
||||
MenuSelectedIndex = -1;
|
||||
// Удаляем несуществующий элемент из списка
|
||||
MenuItems.RemoveAt(nonExistingIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем изображение в базе данных
|
||||
var imageRecord = _context.ImageRecords.Find(selectedRecord.ImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (imageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.ImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.Image.Data = imageRecord?.Base64 ?? "";
|
||||
_logger.LogTrace("Выбран элемент из списка: {}", value);
|
||||
// Находим запись в базе данных
|
||||
var selectedRecord = _context.ResultRecords.Find(MenuItems[value].Id);
|
||||
// Если запись найдена, то заполняем поля из базы данных
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
// Заполняем простые поля
|
||||
Record.Id = selectedRecord.Id;
|
||||
Record.Uuid = selectedRecord.Uuid;
|
||||
Record.CreateDateTime = selectedRecord.CreateDateTime;
|
||||
Record.EditDateTime = selectedRecord.EditDateTime;
|
||||
Record.SampleName = selectedRecord.SampleName;
|
||||
Record.SampleComment = selectedRecord.SampleComment;
|
||||
Record.SamplingDateTime = selectedRecord.SamplingDateTime;
|
||||
Record.SamplingPlace = selectedRecord.SamplingPlace;
|
||||
Record.SeparatorType = selectedRecord.SeparatorType;
|
||||
Record.BrandName = selectedRecord.BrandName;
|
||||
Record.MixtureName = selectedRecord.MixtureName;
|
||||
Record.MixtureNormalRate = selectedRecord.MixtureNormalRate;
|
||||
Record.ParticlesCount = selectedRecord.ParticlesCount;
|
||||
Record.Values = selectedRecord.ValueData.Chunk(selectedRecord.ValueCount).Select(v => v.ToArray()).ToList();
|
||||
Record.Xs = selectedRecord.XData.Chunk(selectedRecord.XCount).Select(v => v.ToArray()).ToList();
|
||||
Record.Ys = selectedRecord.YData.Chunk(selectedRecord.YCount).Select(v => v.ToArray()).ToList();
|
||||
|
||||
// Ищем изображение маски в базе данных
|
||||
var maskImageRecord = _context.ImageRecords.Find(selectedRecord.MaskImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (maskImageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.MaskImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.MaskImage.Data = maskImageRecord?.Base64 ?? "";
|
||||
// Ищем изображение в базе данных
|
||||
var imageRecord = _context.ImageRecords.Find(selectedRecord.ImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (imageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.ImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.Image.Data = imageRecord?.Base64 ?? "";
|
||||
|
||||
// Ищем изображение результата в базе данных
|
||||
var resultImageRecord = _context.ImageRecords.Find(selectedRecord.ResultImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (resultImageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.ResultImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.MaskImage.Data = resultImageRecord?.Base64 ?? "";
|
||||
// Ищем изображение маски в базе данных
|
||||
var maskImageRecord = _context.ImageRecords.Find(selectedRecord.MaskImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (maskImageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.MaskImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.MaskImage.Data = maskImageRecord?.Base64 ?? "";
|
||||
|
||||
// Сбрасываем приближение изображений
|
||||
ImagesPanX = 0;
|
||||
ImagesPanY = 0;
|
||||
ImagesZoom = 1;
|
||||
// Ищем изображение результата в базе данных
|
||||
var resultImageRecord = _context.ImageRecords.Find(selectedRecord.ResultImageId);
|
||||
// Если изображение не найдено в базе данных, то логгируем как ошибку
|
||||
if (resultImageRecord is null)
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", selectedRecord.ResultImageId, nameof(_context.ImageRecords));
|
||||
// В любом случае заполняем изображение
|
||||
Record.ResultImage.Data = resultImageRecord?.Base64 ?? "";
|
||||
|
||||
// Устанавливаем, что запись выбрана из базы данных
|
||||
IsNewRecord = false;
|
||||
// Вычисляем дополнительные свойства
|
||||
Record.CalculateProperties();
|
||||
// Сбрасываем приближение изображений
|
||||
ImagesPanX = 0;
|
||||
ImagesPanY = 0;
|
||||
ImagesZoom = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
// Если значение не найдено в базе данных
|
||||
else
|
||||
{
|
||||
// Логгируем как ошибку
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", MenuItems[value].Id, nameof(_context.BrandRecords));
|
||||
var nonExistingIndex = MenuSelectedIndex; // Сохраняем несуществующий индекс
|
||||
// Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
|
||||
MenuSelectedIndex = -1;
|
||||
// Удаляем несуществующий элемент из списка
|
||||
MenuItems.RemoveAt(nonExistingIndex);
|
||||
return;
|
||||
}
|
||||
// Устанавливаем, что запись выбрана из базы данных
|
||||
IsNewRecord = false;
|
||||
// Вычисляем дополнительные свойства
|
||||
Record.CalculateProperties();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
@@ -1056,6 +1119,84 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
Record.CalculateProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при изменении <see cref="BrandComboBoxSelectedIndex"/>
|
||||
/// </summary>
|
||||
partial void OnBrandComboBoxSelectedIndexChanged(int value)
|
||||
{
|
||||
if (!IsNewRecord)
|
||||
return;
|
||||
// Если значение выходит за верхнюю границу списка элементов выпадающего списка марок, то логгируем как ошибку
|
||||
if (value >= BrandComboBoxItems.Count())
|
||||
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(BrandComboBoxSelectedIndex), BrandComboBoxSelectedIndex, nameof(BrandComboBoxItems), BrandComboBoxItems.Count());
|
||||
// Если значение меньше 0, то значит элемент выпадающего списка марок не выбран
|
||||
if (value < 0 || value >= BrandComboBoxItems.Count())
|
||||
{
|
||||
// Логгируем как ошибку
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", BrandComboBoxItems[value].Id, nameof(_context.BrandRecords));
|
||||
// Сохраняем несуществующий индекс
|
||||
var nonExistingIndex = BrandComboBoxSelectedIndex;
|
||||
// Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
|
||||
BrandComboBoxSelectedIndex = -1;
|
||||
// Удаляем несуществующий элемент из списка
|
||||
BrandComboBoxItems.RemoveAt(nonExistingIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим запись в базе данных
|
||||
var selectedRecord = _context.BrandRecords.Find(BrandComboBoxItems[value].Id);
|
||||
// Если запись найдена, то заполняем поля из базы данных
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
Record.BrandName = selectedRecord.BrandName;
|
||||
Record.MixtureName = selectedRecord.MixtureName;
|
||||
Record.MixtureNormalRate = selectedRecord.MixtureNormalRate;
|
||||
return;
|
||||
}
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
Record.BrandName = "";
|
||||
Record.MixtureName = "";
|
||||
Record.MixtureNormalRate = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при изменении <see cref="SeparatorComboBoxSelectedIndex"/>
|
||||
/// </summary>
|
||||
partial void OnSeparatorComboBoxSelectedIndexChanged(int value)
|
||||
{
|
||||
if (!IsNewRecord)
|
||||
return;
|
||||
// Если значение выходит за верхнюю границу списка элементов выпадающего списка сепараторов, то логгируем как ошибку
|
||||
if (value >= SeparatorComboBoxItems.Count())
|
||||
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(SeparatorComboBoxSelectedIndex), SeparatorComboBoxSelectedIndex, nameof(SeparatorComboBoxItems), SeparatorComboBoxItems.Count());
|
||||
// Если значение меньше 0, то значит элемент выпадающего списка сепараторов не выбран
|
||||
if (value < 0 || value >= SeparatorComboBoxItems.Count())
|
||||
{
|
||||
// Логгируем как ошибку
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SeparatorComboBoxItems[value].Id, nameof(_context.SeparatorRecords));
|
||||
// Сохраняем несуществующий индекс
|
||||
var nonExistingIndex = SeparatorComboBoxSelectedIndex;
|
||||
// Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
|
||||
SeparatorComboBoxSelectedIndex = -1;
|
||||
// Удаляем несуществующий элемент из списка
|
||||
SeparatorComboBoxItems.RemoveAt(nonExistingIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Находим запись в базе данных
|
||||
var selectedRecord = _context.SeparatorRecords.Find(SeparatorComboBoxItems[value].Id);
|
||||
// Если запись найдена, то заполняем поля из базы данных
|
||||
if (selectedRecord is not null)
|
||||
{
|
||||
Record.SeparatorType = selectedRecord.Type;
|
||||
return;
|
||||
}
|
||||
|
||||
// Если элемент не выбран, заполняем поля пустыми значениями
|
||||
Record.SeparatorType = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить значение <see cref="CanInteract"/>
|
||||
/// </summary>
|
||||
@@ -1386,11 +1527,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
if (attachedImageRecord is null)
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
if (!string.IsNullOrEmpty(Record.MaskImage.Data))
|
||||
{
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
var imageRecord = new ImageRecord(base64: Record.MaskImage.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
attachedImageRecord = _context.ImageRecords.Attach(imageRecord).Entity;
|
||||
// Сохраняем изменения в базе данных
|
||||
@@ -1403,11 +1544,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
else
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
if (!string.IsNullOrEmpty(Record.MaskImage.Data))
|
||||
{
|
||||
_logger.LogTrace("Записываем изменения в запись изображения");
|
||||
// Вносим изменения
|
||||
attachedImageRecord.Base64 = Record.Image.Data;
|
||||
attachedImageRecord.Base64 = Record.MaskImage.Data;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
}
|
||||
@@ -1430,11 +1571,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
if (attachedImageRecord is null)
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
if (!string.IsNullOrEmpty(Record.ResultImage.Data))
|
||||
{
|
||||
_logger.LogTrace("Создаём новую запись изображения");
|
||||
// Заполняем запись изображения
|
||||
var imageRecord = new ImageRecord(base64: Record.Image.Data);
|
||||
var imageRecord = new ImageRecord(base64: Record.ResultImage.Data);
|
||||
// Добавляем запись изображения в базу данных
|
||||
attachedImageRecord = _context.ImageRecords.Attach(imageRecord).Entity;
|
||||
// Сохраняем изменения в базе данных
|
||||
@@ -1447,11 +1588,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
else
|
||||
{
|
||||
// Если изображение есть
|
||||
if (!string.IsNullOrEmpty(Record.Image.Data))
|
||||
if (!string.IsNullOrEmpty(Record.ResultImage.Data))
|
||||
{
|
||||
_logger.LogTrace("Записываем изменения в запись изображения");
|
||||
// Вносим изменения
|
||||
attachedImageRecord.Base64 = Record.Image.Data;
|
||||
attachedImageRecord.Base64 = Record.ResultImage.Data;
|
||||
// Сохраняем изменения в базе данных
|
||||
_context.SaveChanges();
|
||||
}
|
||||
@@ -1561,7 +1702,7 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
// Выбрасываем исключение, если задача отменена
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
// Сохраняем PNG изображение во временную папку
|
||||
var path = Path.Join(Path.GetTempPath(), $"gss2-calibration-sample-{visible}-{uv365}-{uv254}.png");
|
||||
var path = Path.Join(Path.GetTempPath(), $"gss2-analysis-{visible}-{uv365}-{uv254}.png");
|
||||
image.ImWrite(path);
|
||||
|
||||
// Освобождаем память изображения
|
||||
@@ -1574,6 +1715,18 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
imagesData.Add(imagesDataTask);
|
||||
}, cancellationToken);
|
||||
|
||||
// var captureTask = Task.Run(() => {
|
||||
// foreach (var (visible, uv365, uv254) in ImageCapturerService.IlluminatorIntensities)
|
||||
// {
|
||||
// var imagesDataTask = Task.Run(() =>
|
||||
// {
|
||||
// var path = Path.Join(Path.GetTempPath(), $"gss2-analysis-{visible}-{uv365}-{uv254}.png");
|
||||
// return new ImageData(path, visible, uv365, uv254);
|
||||
// }, cancellationToken);
|
||||
// imagesData.Add(imagesDataTask);
|
||||
// }
|
||||
// });
|
||||
|
||||
// Создаём задачу обработки изображений,
|
||||
// эта задача начнётся только после того как завершится
|
||||
// задача съёмки изображений и все задачи сохранения изображений
|
||||
@@ -1628,6 +1781,11 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", BrandComboBoxItems[BrandComboBoxSelectedIndex].Id, nameof(_context.BrandRecords));
|
||||
return;
|
||||
}
|
||||
if (brand.MixtureNormalRate <= 0)
|
||||
{
|
||||
_logger.LogError("Норма расхода выбранной марки меньше или равена 0");
|
||||
return;
|
||||
}
|
||||
if (separator is null)
|
||||
{
|
||||
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", SeparatorComboBoxItems[SeparatorComboBoxSelectedIndex].Id, nameof(_context.SeparatorRecords));
|
||||
@@ -1662,13 +1820,16 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
await Task.Delay(3000);
|
||||
_lights.Disconnect();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Распаковываем результат
|
||||
var (avgImage, _, mask, contours, resultImage, separatorType, sampleBrand) = result.Value;
|
||||
var (avgImage, _, mask, contours, resultImage) = result.Value;
|
||||
|
||||
try
|
||||
{
|
||||
Record.ParticlesCount = contours.Count();
|
||||
|
||||
// Кодируем среднее изображение
|
||||
OpenCvSharp.Cv2.ImEncode(".png", avgImage, out var avgData);
|
||||
Record.Image.Data = Convert.ToBase64String(avgData);
|
||||
@@ -1685,7 +1846,7 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
new OpenCvSharp.Scalar(0)
|
||||
);
|
||||
|
||||
Record.Values = new List<float[]>();
|
||||
var values = new List<float[]>();
|
||||
// Проходим по каждому контуру
|
||||
foreach (var contour in contours)
|
||||
{
|
||||
@@ -1704,8 +1865,9 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
if (contourMask.Get<byte>(i, j) > 127)
|
||||
contourValues.Add(resultImage.Get<float>(i, j));
|
||||
// Сохраняем полученные значения
|
||||
Record.Values.Add(contourValues.ToArray());
|
||||
values.Add(contourValues.ToArray());
|
||||
}
|
||||
Record.Values = values;
|
||||
// Освобождаем память маски контура
|
||||
contourMask.Dispose();
|
||||
|
||||
@@ -1714,65 +1876,30 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
Record.Ys = contours.Select(c => c.Select(p => (float)p.Y).ToArray()).ToList();
|
||||
|
||||
// Вычисляем пороговые значения
|
||||
var unprocessedThreshold = RecordViewModel.UNPROCESSED_THRESHOLD * Record.MixtureNormalRate;
|
||||
var hotspotsThreshold = RecordViewModel.HOTSPOT_THRESHOLD * Record.MixtureNormalRate;
|
||||
var unprocessedThreshold = RecordViewModel.UNPROCESSED_THRESHOLD * brand.MixtureNormalRate;
|
||||
var hotspotsThreshold = RecordViewModel.HOTSPOT_THRESHOLD * brand.MixtureNormalRate;
|
||||
|
||||
// Выичсляем максимальное значение
|
||||
OpenCvSharp.Cv2.MinMaxLoc(resultImage, out _, out double max);
|
||||
// Если иаксмиальное значниеz слишком мало, то устанавливаем его чуть больше hotspotsThreshold, чтобы не возникло деления на ноль и отридцательных значений
|
||||
if (max <= hotspotsThreshold)
|
||||
max = hotspotsThreshold + 0.001;
|
||||
var lutIndices = new OpenCvSharp.Mat(resultImage.Size(), OpenCvSharp.MatType.CV_8UC1, OpenCvSharp.Scalar.All(0));
|
||||
|
||||
using (var mask1 = new OpenCvSharp.Mat())
|
||||
{
|
||||
OpenCvSharp.Cv2.InRange(resultImage, OpenCvSharp.Scalar.All(0), OpenCvSharp.Scalar.All(unprocessedThreshold), mask1);
|
||||
// Создаём цветное изображение результата
|
||||
var coloredResultImage = new OpenCvSharp.Mat(resultImage.Size(), OpenCvSharp.MatType.CV_8UC3, OpenCvSharp.Scalar.All(0));
|
||||
// Расшрашиваем диапазон 0...unprocessedThreshold -> чёрный...белый
|
||||
ApplyGradientToRange(resultImage, 0, unprocessedThreshold, OpenCvSharp.Scalar.Black, OpenCvSharp.Scalar.White, coloredResultImage);
|
||||
// Расшрашиваем диапазон unprocessedThreshold...hotspotsThreshold -> белый...зелёный
|
||||
ApplyGradientToRange(resultImage, unprocessedThreshold, hotspotsThreshold, OpenCvSharp.Scalar.White, OpenCvSharp.Scalar.Green, coloredResultImage);
|
||||
// Расшрашиваем диапазон hotspotsThreshold...max -> зелёный...крастный
|
||||
ApplyGradientToRange(resultImage, hotspotsThreshold, max, OpenCvSharp.Scalar.Green, OpenCvSharp.Scalar.Red, coloredResultImage);
|
||||
|
||||
var scale1 = 85.0 / unprocessedThreshold;
|
||||
|
||||
using (var res1 = new OpenCvSharp.Mat())
|
||||
{
|
||||
resultImage.ConvertTo(res1, OpenCvSharp.MatType.CV_8UC1, scale1);
|
||||
res1.CopyTo(lutIndices, mask1);
|
||||
}
|
||||
}
|
||||
using (var mask2 = new OpenCvSharp.Mat())
|
||||
{
|
||||
OpenCvSharp.Cv2.InRange(resultImage, OpenCvSharp.Scalar.All(unprocessedThreshold), OpenCvSharp.Scalar.All(hotspotsThreshold), mask2);
|
||||
|
||||
double scale2 = 85.0 / (hotspotsThreshold - unprocessedThreshold);
|
||||
double shift2 = -unprocessedThreshold * scale2 + 85.0;
|
||||
|
||||
using (var res2 = new OpenCvSharp.Mat())
|
||||
{
|
||||
resultImage.ConvertTo(res2, OpenCvSharp.MatType.CV_8UC1, scale2, shift2);
|
||||
res2.CopyTo(lutIndices, mask2);
|
||||
}
|
||||
}
|
||||
using (var mask3 = new OpenCvSharp.Mat())
|
||||
{
|
||||
OpenCvSharp.Cv2.InRange(resultImage, OpenCvSharp.Scalar.All(hotspotsThreshold), OpenCvSharp.Scalar.All(max), mask3);
|
||||
|
||||
double scale3 = 85.0 / (max - hotspotsThreshold);
|
||||
double shift3 = -hotspotsThreshold * scale3 + 170.0;
|
||||
|
||||
using (var res3 = new OpenCvSharp.Mat())
|
||||
{
|
||||
resultImage.ConvertTo(res3, OpenCvSharp.MatType.CV_8UC1, scale3, shift3);
|
||||
res3.CopyTo(lutIndices, mask3);
|
||||
}
|
||||
}
|
||||
|
||||
// Создаём и применяем LUT
|
||||
var lut = CreateLut();
|
||||
var coloredResult = new OpenCvSharp.Mat();
|
||||
OpenCvSharp.Cv2.LUT(lutIndices, lut, coloredResult);
|
||||
// Устанавливаем в 0 всё что не попадает в маску
|
||||
var invMask = new OpenCvSharp.Mat(mask.Size(), mask.Type(), OpenCvSharp.Scalar.All(0));
|
||||
OpenCvSharp.Cv2.BitwiseNot(mask, invMask);
|
||||
coloredResult.SetTo(OpenCvSharp.Scalar.All(0), invMask);
|
||||
coloredResultImage.SetTo(OpenCvSharp.Scalar.All(0), invMask);
|
||||
|
||||
// Освобождаем память, оригинальное изображение результата можно удалить
|
||||
lutIndices.Dispose();
|
||||
lut.Dispose();
|
||||
invMask.Dispose();
|
||||
resultImage.Dispose();
|
||||
|
||||
@@ -1783,10 +1910,10 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
mask.Release();
|
||||
|
||||
// Кодируем изображение результата
|
||||
OpenCvSharp.Cv2.ImEncode(".png", coloredResult, out var resultData);
|
||||
OpenCvSharp.Cv2.ImEncode(".png", coloredResultImage, out var resultData);
|
||||
Record.ResultImage.Data = Convert.ToBase64String(resultData);
|
||||
// Освобождаем память изображения результата
|
||||
coloredResult.Release();
|
||||
coloredResultImage.Release();
|
||||
|
||||
// Пересчитываем значения свойств
|
||||
Record.CalculateProperties();
|
||||
@@ -1883,32 +2010,52 @@ public partial class AnalysisViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
[RelayCommand] private void CancelImageCapturing() => _captureImagesTaskCts?.Cancel();
|
||||
|
||||
private static OpenCvSharp.Mat CreateLut()
|
||||
/// <summary>
|
||||
/// Раскрасить диапазон в градиент
|
||||
/// </summary>
|
||||
public static void ApplyGradientToRange(OpenCvSharp.Mat source, double minVal, double maxVal, OpenCvSharp.Scalar minColor, OpenCvSharp.Scalar maxColor, OpenCvSharp.Mat destination)
|
||||
{
|
||||
var lut = new OpenCvSharp.Mat(256, 1, OpenCvSharp.MatType.CV_8UC3);
|
||||
var indexer = lut.GetGenericIndexer<OpenCvSharp.Vec3b>();
|
||||
// Проверяем что все типы данных
|
||||
if (source.Type() != OpenCvSharp.MatType.CV_32FC1)
|
||||
throw new ArgumentException("Исходное изображение должно быть типа CV_32FC1", nameof(source));
|
||||
if (destination.Type() != OpenCvSharp.MatType.CV_8UC3)
|
||||
throw new ArgumentException("Выходное изображение должно быть типа CV_8UC3", nameof(destination));
|
||||
if (source.Size() != destination.Size())
|
||||
throw new ArgumentException("Размеры исходного и выходного изображений должны совпадать");
|
||||
if (maxVal <= minVal)
|
||||
throw new ArgumentException("Максимальное значение диапазона должно быть больше минимального.");
|
||||
|
||||
for (int i = 0; i <= 85; i++)
|
||||
{
|
||||
var val = (byte)Math.Round(i * (255.0 / 85.0));
|
||||
indexer[i, 0] = new OpenCvSharp.Vec3b(val, val, val);
|
||||
}
|
||||
// Создаем маску для целевого диапазона значений
|
||||
using var mask = new OpenCvSharp.Mat();
|
||||
OpenCvSharp.Cv2.InRange(source, OpenCvSharp.Scalar.All(minVal), OpenCvSharp.Scalar.All(maxVal), mask);
|
||||
|
||||
for (int i = 86; i <= 170; i++)
|
||||
{
|
||||
var factor = (i - 85) / 85.0; // от 0 до 1
|
||||
var rbVal = (byte)Math.Round(255 * (1.0 - factor));
|
||||
indexer[i, 0] = new OpenCvSharp.Vec3b(rbVal, 255, rbVal);
|
||||
}
|
||||
// Нормализуем значения из диапазона minVal...maxVal в 0.0...1.0
|
||||
using var normalized = new OpenCvSharp.Mat();
|
||||
OpenCvSharp.Cv2.Subtract(source, OpenCvSharp.Scalar.All(minVal), normalized);
|
||||
normalized.ConvertTo(normalized, OpenCvSharp.MatType.CV_32FC1, 1.0 / (maxVal - minVal));
|
||||
|
||||
for (int i = 171; i <= 255; i++)
|
||||
{
|
||||
var factor = (i - 170) / 85.0; // от 0 до 1
|
||||
var gVal = (byte)Math.Round(255 * (1.0 - factor));
|
||||
var rVal = (byte)Math.Round(255 * factor);
|
||||
indexer[i, 0] = new OpenCvSharp.Vec3b(0, gVal, rVal); // BGR
|
||||
}
|
||||
// Чтобы избежать выхода за границы, ограничиваем диапазон от 0.0...1.0
|
||||
OpenCvSharp.Cv2.Threshold(normalized, normalized, 1.0, 1.0, OpenCvSharp.ThresholdTypes.Trunc);
|
||||
OpenCvSharp.Cv2.Threshold(normalized, normalized, 0.0, 0.0, OpenCvSharp.ThresholdTypes.Tozero);
|
||||
|
||||
return lut;
|
||||
// Вычисляем градиент для каждого из трех каналов (BGR)
|
||||
using var bChannel = new OpenCvSharp.Mat();
|
||||
using var gChannel = new OpenCvSharp.Mat();
|
||||
using var rChannel = new OpenCvSharp.Mat();
|
||||
|
||||
double diffB = maxColor.Val0 - minColor.Val0;
|
||||
double diffG = maxColor.Val1 - minColor.Val1;
|
||||
double diffR = maxColor.Val2 - minColor.Val2;
|
||||
|
||||
normalized.ConvertTo(bChannel, OpenCvSharp.MatType.CV_8UC1, diffB, minColor.Val0);
|
||||
normalized.ConvertTo(gChannel, OpenCvSharp.MatType.CV_8UC1, diffG, minColor.Val1);
|
||||
normalized.ConvertTo(rChannel, OpenCvSharp.MatType.CV_8UC1, diffR, minColor.Val2);
|
||||
|
||||
// Объеденяем каналы в одно изображение
|
||||
using var gradientColor = new OpenCvSharp.Mat();
|
||||
OpenCvSharp.Cv2.Merge(new[] { bChannel, gChannel, rChannel }, gradientColor);
|
||||
|
||||
// Копируем полученный градиент в итоговое изображение по маске
|
||||
gradientColor.CopyTo(destination, mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +185,8 @@
|
||||
IsReadOnly="True"
|
||||
IsVisible="{Binding !IsNewRecord}"
|
||||
PlaceholderText="Тип сепаратора"
|
||||
>
|
||||
<!-- <Run Text="{Binding Record.SeparatorType}" /> -->
|
||||
</TextBox>
|
||||
Text="{Binding Record.SeparatorType}"
|
||||
/>
|
||||
<!-- BrandComboBox (подпись) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
@@ -229,12 +228,13 @@
|
||||
IsVisible="{Binding !IsNewRecord}"
|
||||
PlaceholderText="Марка пробы"
|
||||
>
|
||||
<!-- <Run Text="{Binding Record.BrandName}" />
|
||||
<Run Text=" - " />
|
||||
<Run Text="{Binding Record.MixtureName}" />
|
||||
<Run Text=" (" />
|
||||
<Run Text="{Binding Record.MixtureNormalRate}" />
|
||||
<Run Text=" кг/т)" /> -->
|
||||
<TextBox.Text>
|
||||
<MultiBinding StringFormat="{}{0} - {1} ({2} кг/т)" Mode="OneWay">
|
||||
<Binding Path="Record.BrandName" />
|
||||
<Binding Path="Record.MixtureName" />
|
||||
<Binding Path="Record.MixtureNormalRate" />
|
||||
</MultiBinding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<!-- SampleName (подпись) -->
|
||||
@@ -365,234 +365,238 @@
|
||||
<TextBlock Text="3"/>
|
||||
</Button>
|
||||
|
||||
<!-- Изображения -->
|
||||
<!-- Изображения -->
|
||||
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="3">
|
||||
|
||||
<!-- Изображения -->
|
||||
<Grid>
|
||||
<!-- Image -->
|
||||
<Grid IsVisible="{Binding ImageIsVisible}" IsEnabled="{Binding ImageIsVisible}">
|
||||
|
||||
<!-- Изображения -->
|
||||
<!-- Image -->
|
||||
<Grid IsVisible="{Binding ImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.Image.Image}"
|
||||
PanX="{Binding ImagesPanX, Mode=TwoWay}"
|
||||
PanY="{Binding ImagesPanY, Mode=TwoWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding ImagesZoom, Mode=TwoWay}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.Image.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.Image.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<!-- MaskImage -->
|
||||
<Grid IsVisible="{Binding MaskImageIsVisible}" IsEnabled="{Binding MaskImageIsVisible}">
|
||||
|
||||
<!-- MaskImage -->
|
||||
<Grid IsVisible="{Binding MaskImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.MaskImage.Image}"
|
||||
PanX="{Binding ImagesPanX, Mode=TwoWay}"
|
||||
PanY="{Binding ImagesPanY, Mode=TwoWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding ImagesZoom, Mode=TwoWay}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.MaskImage.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.MaskImage.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<!-- ResultImage -->
|
||||
<Grid IsVisible="{Binding ResultImageIsVisible}" IsEnabled="{Binding ResultImageIsVisible}">
|
||||
|
||||
<!-- ResultImage -->
|
||||
<Grid IsVisible="{Binding ResultImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ResultImage.Image}"
|
||||
PanX="{Binding ImagesPanX, Mode=TwoWay}"
|
||||
PanY="{Binding ImagesPanY, Mode=TwoWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding ImagesZoom, Mode=TwoWay}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ResultImage.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
|
||||
<!-- Оверлей "Изображение загружается" -->
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" >
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="Изображение загружается" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
|
||||
<!-- Оверлей прогресс -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
<Binding Converter="{x:Static BoolConverters.Not}" Path="Record.ResultImage.IsLoading" />
|
||||
<Binding Path="ImagesIsCapturing" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
|
||||
<ProgressBar IsIndeterminate="True" />
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding ImagesCapturingProgress}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -657,7 +661,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Средняя норм. обр. площадь"
|
||||
Text="{Binding Record.MeanProcessedArea}"
|
||||
Text="{Binding Record.MeanProcessedArea, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- ProcessedAreaP50 (подпись) -->
|
||||
<TextBlock
|
||||
@@ -673,7 +677,7 @@
|
||||
Grid.Row="1"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="XXX"
|
||||
Text="{Binding Record.ProcessedAreaP50}"
|
||||
Text="{Binding Record.ProcessedAreaP50, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- ProcessedAreaP10 (подпись) -->
|
||||
<TextBlock
|
||||
@@ -689,7 +693,7 @@
|
||||
Grid.Row="3"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="10-й процентиль"
|
||||
Text="{Binding Record.ProcessedAreaP10}"
|
||||
Text="{Binding Record.ProcessedAreaP10, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- ProcessedAreaP90 (подпись) -->
|
||||
<TextBlock
|
||||
@@ -705,7 +709,7 @@
|
||||
Grid.Row="3"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="90-й процентиль"
|
||||
Text="{Binding Record.ProcessedAreaP90}"
|
||||
Text="{Binding Record.ProcessedAreaP90, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- ProcessedAreaDeltaP90P10 (подпись) -->
|
||||
<TextBlock
|
||||
@@ -721,7 +725,7 @@
|
||||
Grid.Row="3"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Ширина центрального диапазона"
|
||||
Text="{Binding Record.ProcessedAreaDeltaP90P10}"
|
||||
Text="{Binding Record.ProcessedAreaDeltaP90P10, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- ProcessedAreaStdDev (подпись) -->
|
||||
<TextBlock
|
||||
@@ -737,7 +741,7 @@
|
||||
Grid.Row="5"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Межгранульное СКО"
|
||||
Text="{Binding Record.ProcessedAreaStdDev}"
|
||||
Text="{Binding Record.ProcessedAreaStdDev, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- InterHeterogeneity (подпись) -->
|
||||
<TextBlock
|
||||
@@ -753,7 +757,7 @@
|
||||
Grid.Row="5"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Межгранульная неоднородность"
|
||||
Text="{Binding Record.InterHeterogeneity}"
|
||||
Text="{Binding Record.InterHeterogeneity, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- UnprocessedFraction (подпись) -->
|
||||
<TextBlock
|
||||
@@ -769,7 +773,7 @@
|
||||
Grid.Row="5"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Доля необработанных гранул"
|
||||
Text="{Binding Record.UnprocessedFraction}"
|
||||
Text="{Binding Record.UnprocessedFraction, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- MeanInnerHeterogeneity (подпись) -->
|
||||
<TextBlock
|
||||
@@ -785,7 +789,7 @@
|
||||
Grid.Row="7"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Ср. внутригранульная однородность"
|
||||
Text="{Binding Record.MeanInnerHeterogeneity}"
|
||||
Text="{Binding Record.MeanInnerHeterogeneity, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- MeanHotspotArea (подпись) -->
|
||||
<TextBlock
|
||||
@@ -801,7 +805,7 @@
|
||||
Grid.Row="7"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Ср. горячая площадь"
|
||||
Text="{Binding Record.MeanHotspotArea}"
|
||||
Text="{Binding Record.MeanHotspotArea, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
<!-- MeanOpticalIntegral (подпись) -->
|
||||
<TextBlock
|
||||
@@ -817,7 +821,7 @@
|
||||
Grid.Row="7"
|
||||
IsReadOnly="True"
|
||||
PlaceholderText="Ср. инт. опт. показатель"
|
||||
Text="{Binding Record.MeanOpticalIntegral}"
|
||||
Text="{Binding Record.MeanOpticalIntegral, StringFormat={}{0:F4}}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
@@ -856,30 +860,6 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Гистограмма нормированных необработанных площадей -->
|
||||
<Grid>
|
||||
|
||||
<!-- Гистограмма -->
|
||||
<lvc:CartesianChart
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding Path=Record.ParticlesCount, Converter={StaticResource GreaterThanConverter}, ConverterParameter=0}"
|
||||
Series="{Binding Record.UnprocessedAreasHistogram.Series, Mode=OneWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
x:Name="UnprocessedAreasHistogram"
|
||||
XAxes="{Binding Record.UnprocessedAreasHistogram.XAxis, Mode=OneWay}"
|
||||
YAxes="{Binding Record.UnprocessedAreasHistogram.YAxis, Mode=OneWay}"
|
||||
/>
|
||||
|
||||
<!-- Оверлей "Нет информации" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding Path=Record.ParticlesCount, Converter={StaticResource LessThanOrEqualConverter}, ConverterParameter=0}"
|
||||
Text="Нет информации"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Гистограмма нормированных площадей хотспотов -->
|
||||
<Grid>
|
||||
|
||||
@@ -1133,175 +1113,176 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Изображения -->
|
||||
<Grid Grid.Column="1">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/> <!-- Изображения -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- Кнопки -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" /> <!-- Изображения -->
|
||||
<RowDefinition Height="*" /> <!-- Гистограмма -->
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
|
||||
<!-- Изображения -->
|
||||
<Grid Grid.Row="0">
|
||||
<!-- Кнопки переключения -->
|
||||
<!-- ParticleImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="1"/>
|
||||
</Button>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/> <!-- Изображения -->
|
||||
<ColumnDefinition Width="Auto"/> <!-- Кнопки -->
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- ParticleMaskImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToMaskImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="2"/>
|
||||
</Button>
|
||||
|
||||
<Grid.ColumnSpacing>5</Grid.ColumnSpacing>
|
||||
<!-- ParticleResultImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToResultImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="3"/>
|
||||
</Button>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Изображения -->
|
||||
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="3">
|
||||
|
||||
<Grid.RowSpacing>5</Grid.RowSpacing>
|
||||
|
||||
<!-- Кнопки переключения -->
|
||||
<!-- ParticleImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="1"/>
|
||||
</Button>
|
||||
|
||||
<!-- ParticleMaskImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToMaskImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="2"/>
|
||||
</Button>
|
||||
|
||||
<!-- ParticleResultImage -->
|
||||
<Button
|
||||
Classes="vertical"
|
||||
Command="{Binding Record.SwitchToResultImageCommand}"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<TextBlock Text="3"/>
|
||||
</Button>
|
||||
|
||||
<!-- Изображения -->
|
||||
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="3">
|
||||
<!-- Изображения -->
|
||||
<Grid x:Name="PraticleImagesGrid" Bounds="{Binding Record.ParticleImagesBounds, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}">
|
||||
|
||||
<!-- Изображения -->
|
||||
<Grid>
|
||||
<!-- ParticleImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleImageIsVisible}">
|
||||
|
||||
<!-- Изображения -->
|
||||
<!-- ParticleImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
CanUserZoomPan="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.Image.Image}"
|
||||
PanX="{Binding Record.ParticleImagesPanX}"
|
||||
PanY="{Binding Record.ParticleImagesPanY}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding Record.ParticleImagesZoom}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ParticleImage.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.Image.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ParticleImage.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.Image.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ParticleImage.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<!-- ParticleMaskImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleMaskImageIsVisible}">
|
||||
|
||||
<!-- ParticleMaskImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleMaskImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
CanUserZoomPan="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.MaskImage.Image}"
|
||||
PanX="{Binding Record.ParticleImagesPanX}"
|
||||
PanY="{Binding Record.ParticleImagesPanY}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding Record.ParticleImagesZoom}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ParticleMaskImage.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ParticleMaskImage.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.MaskImage.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ParticleMaskImage.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<!-- ParticleResultImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleResultImageIsVisible}">
|
||||
|
||||
<!-- ParticleResultImage -->
|
||||
<Grid IsVisible="{Binding Record.ParticleResultImageIsVisible}">
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
CanUserZoomPan="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ResultImage.Image}"
|
||||
PanX="{Binding Record.ParticleImagesPanX}"
|
||||
PanY="{Binding Record.ParticleImagesPanY}"
|
||||
VerticalAlignment="Stretch"
|
||||
Zoom="{Binding Record.ParticleImagesZoom}"
|
||||
>
|
||||
|
||||
<!-- Изображение -->
|
||||
<core:ZoomPanImage
|
||||
Background="Black"
|
||||
HorizontalAlignment="Stretch"
|
||||
ImageSource="{Binding Record.ParticleResultImage.Image}"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
|
||||
<core:ZoomPanImage.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNotNullOrEmpty}" Path="Record.ParticleResultImage.Data" />
|
||||
</MultiBinding>
|
||||
</core:ZoomPanImage.IsVisible>
|
||||
</core:ZoomPanImage>
|
||||
|
||||
</core:ZoomPanImage>
|
||||
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ParticleResultImage.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
</Grid>
|
||||
<!-- Оверлей "Изображение отсутствует" -->
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="Изображение отсутствует"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Converter="{x:Static StringConverters.IsNullOrEmpty}" Path="Record.ResultImage.Data" />
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -1309,17 +1290,6 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<lvc:CartesianChart
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="{Binding Path=Record.ParticlesCount, Converter={StaticResource GreaterThanConverter}, ConverterParameter=0}"
|
||||
Series="{Binding Record.ParticleValuesHistogram.Series, Mode=OneWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
x:Name="ParticleValuesHistogram"
|
||||
XAxes="{Binding Record.ParticleValuesHistogram.XAxis, Mode=OneWay}"
|
||||
YAxes="{Binding Record.ParticleValuesHistogram.YAxis, Mode=OneWay}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -17,11 +17,9 @@ public partial class AnalysisView : UserControl
|
||||
private void OnChartsUpdate(object? sender, EventArgs ea)
|
||||
{
|
||||
ProcessedAreasHistogram.CoreChart.Update();
|
||||
UnprocessedAreasHistogram.CoreChart.Update();
|
||||
HotspotAreasHistogram.CoreChart.Update();
|
||||
OpticalIntegralsHistogram.CoreChart.Update();
|
||||
InnerStdDevsHistogram.CoreChart.Update();
|
||||
InnerHeterogeneitiesHistogram.CoreChart.Update();
|
||||
ParticleValuesHistogram.CoreChart.Update();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user