forked from amkovkov/GranuSightSoftware2
feat: massive rework
1) GSS2.Core: - strip prefixes in GSS2.Core.Analysis.AmineContent namespace class names - add IEnumerable<double>.Variance extension - remake analysis + update database so not needed to store all every file, calibration data also stored in database, also currently capturing images stored in system temporary directory and rewriting every time 2) GSS.UI.Core: AsyncImageRecordViewModel make zoom and pans public 3) GSS: - remove image-storage-clear command - remove ResultsView and ResultsViewModel, results presented in analysis - rework calibration views - add analysis views - add text and number fields editors view - add confirmation view on danger buttons
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,534 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.ML;
|
||||
using Microsoft.ML.Data;
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
using GSS2.Core.Extensions;
|
||||
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent;
|
||||
|
||||
public partial class AnalyzerService
|
||||
{
|
||||
//TODO: Перенести все гиперпараметры в appsettings.json
|
||||
public const int CHANNELS_COUNT = 3;
|
||||
public const int IMAGES_COUNT = 12;
|
||||
public const int FEATURES_LENGTH = (CHANNELS_COUNT + 3) * IMAGES_COUNT;
|
||||
public const int EFFECTIVE_IMAGE_SIZE = 200;
|
||||
public const int ANALYSIS_IMAGE_SIZE = 1000;
|
||||
public const int SEPARATOR_ROI_THRESHOLD = 5;
|
||||
|
||||
public const float CONTOUR_MAX_SIZE = 100;
|
||||
public const float CONTOUR_MAX_AREA = (float)Math.PI * (CONTOUR_MAX_SIZE * CONTOUR_MAX_SIZE) / 4.0f;
|
||||
public const float CONTOUR_MIN_SIZE = 12;
|
||||
public const float CONTOUR_MIN_AREA = (float)Math.PI * (CONTOUR_MIN_SIZE * CONTOUR_MIN_SIZE) / 4.0f / 4.0f;
|
||||
|
||||
public const int SEPARATOR_X = 519;
|
||||
public const int SEPARATOR_Y = 17;
|
||||
public const int SEPARATOR_WIDTH = 2989;
|
||||
public const int SEPARATOR_HEIGHT = 2985;
|
||||
|
||||
private class ClusterizationData
|
||||
{
|
||||
[ColumnName("Label")]
|
||||
public required string Label { get; set; }
|
||||
[ColumnName("Features")]
|
||||
[VectorType(FEATURES_LENGTH)]
|
||||
public required float[] Features { get; set; }
|
||||
}
|
||||
private class ClusterizationPrediction
|
||||
{
|
||||
[ColumnName("PredictedLabel")]
|
||||
public string Label { get; set; }
|
||||
public float Probability { get; set; }
|
||||
public float[] Score { get; set; }
|
||||
|
||||
public ClusterizationPrediction()
|
||||
{
|
||||
Label = "";
|
||||
Probability = 0;
|
||||
Score = new float[2];
|
||||
}
|
||||
}
|
||||
|
||||
private class RegressionData
|
||||
{
|
||||
[ColumnName("Label")]
|
||||
public required float Value { get; set; }
|
||||
[ColumnName("Features")]
|
||||
[VectorType(FEATURES_LENGTH)]
|
||||
public required float[] Features { get; set; }
|
||||
}
|
||||
private class RegressionPrediction
|
||||
{
|
||||
[ColumnName("Score")]
|
||||
public float Value { get; set; }
|
||||
|
||||
public RegressionPrediction()
|
||||
{
|
||||
Value = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ILogger<AnalyzerService> _logger;
|
||||
private readonly CalibrationContext _calibrationContext;
|
||||
|
||||
private MLContext? _ml = null;
|
||||
private PredictionEngine<ClusterizationData, ClusterizationPrediction>? _clusterizationEngine = null;
|
||||
private PredictionEngine<RegressionData, RegressionPrediction>? _regressionEngine = null;
|
||||
|
||||
public bool Initialized { get; private set; } = false;
|
||||
|
||||
public AnalyzerService(ILogger<AnalyzerService> logger, CalibrationContext calibrationContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_calibrationContext = calibrationContext;
|
||||
}
|
||||
|
||||
public async Task Initialize(string? brand = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Initialized = false;
|
||||
|
||||
_logger.LogInformation("Инициализация");
|
||||
|
||||
_ml = new MLContext();
|
||||
_ml.Log += (_, ea) =>
|
||||
{
|
||||
if (ea.Kind >= Microsoft.ML.Runtime.ChannelMessageKind.Info)
|
||||
_logger.LogInformation("ML: [{}] {}", ea.Kind, ea.Message);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await InitializeMlClusterizationEngine(cancellationToken);
|
||||
await InitializeMlRegressionEngine(brand, cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Исключение во время инициализации моделей");
|
||||
return;
|
||||
}
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
private async Task InitializeMlClusterizationEngine(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Инициализация модели кластеризации");
|
||||
|
||||
var separatorPrefix = "SEPARATOR";
|
||||
var samplePrefix = "SAMPLE";
|
||||
|
||||
if (_ml is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Transforms.Conversion.MapValueToKey("Label")
|
||||
.Append(_ml.MulticlassClassification.Trainers.LbfgsMaximumEntropy())
|
||||
.Append(_ml.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet = _calibrationContext.VectorRecords.Select(r =>
|
||||
new ClusterizationData
|
||||
{
|
||||
Label = r.SeparatorRecord != null ?
|
||||
$"{separatorPrefix}|{r.SeparatorRecord.Type}" :
|
||||
r.SampleRecord != null ?
|
||||
$"{samplePrefix}|{r.SampleRecord.SampleBrand}" :
|
||||
"",
|
||||
Features = r.Values
|
||||
})
|
||||
.ToList();
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
|
||||
_clusterizationEngine = _ml.Model.CreatePredictionEngine<ClusterizationData, ClusterizationPrediction>(model);
|
||||
model.Dispose();
|
||||
}
|
||||
private async Task InitializeMlRegressionEngine(string? brand, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Инициализация модели регрессии для марки \"{}\"", brand);
|
||||
|
||||
if (_ml is null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Создание конвейера");
|
||||
var pipeline = _ml.Regression.Trainers.Sdca();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet =
|
||||
|
||||
_calibrationContext.VectorRecords
|
||||
.Where(r => r.SampleRecord != null)
|
||||
.Where(r => brand == null || r.SampleRecord!.SampleBrand == brand)
|
||||
.Select(r =>
|
||||
new RegressionData
|
||||
{
|
||||
Value = (float)(
|
||||
r.SampleRecord!.MeasuredContent >= 0 ?
|
||||
r.SampleRecord!.MeasuredContent :
|
||||
r.SampleRecord!.MixtureActualRate >= 0 ?
|
||||
r.SampleRecord!.MixtureActualRate :
|
||||
r.SampleRecord!.MixtureNormalRate
|
||||
),
|
||||
Features = r.Values
|
||||
})
|
||||
.ToList();
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Обучение модели");
|
||||
var model = pipeline.Fit(trainData);
|
||||
|
||||
_regressionEngine = _ml.Model.CreatePredictionEngine<RegressionData, RegressionPrediction>(model);
|
||||
|
||||
model.Dispose();
|
||||
}
|
||||
|
||||
public async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, Mat result, string separatorType, string sampleBrand)?> Analyze(IEnumerable<ImageData> imagesData)
|
||||
{
|
||||
if (!Initialized)
|
||||
return null;
|
||||
|
||||
if (imagesData.Count() != IMAGES_COUNT)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var segmentationResult = await Segment(imagesData);
|
||||
if (segmentationResult is null)
|
||||
return null;
|
||||
|
||||
var (avgImage, roi, mask, contours, separatorType, sampleBrand) = segmentationResult.Value;
|
||||
|
||||
var images = new List<(Mat mat, double visible, double uv365, double uv254)>();
|
||||
foreach (var imageData in imagesData
|
||||
.OrderBy(i => i.VisibleIntensity)
|
||||
.ThenBy(i => i.Uv365Intensity)
|
||||
.ThenBy(i => i.Uv254Intensity))
|
||||
images.Add((
|
||||
LoadAndPrepare(imageData.Path, roi, ANALYSIS_IMAGE_SIZE),
|
||||
imageData.VisibleIntensity,
|
||||
imageData.Uv365Intensity,
|
||||
imageData.Uv254Intensity
|
||||
));
|
||||
|
||||
var result = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_32FC1, new Scalar(0));
|
||||
var values = new float[ANALYSIS_IMAGE_SIZE * ANALYSIS_IMAGE_SIZE];
|
||||
|
||||
for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++)
|
||||
for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++)
|
||||
{
|
||||
if (mask.At<byte>(row, col) != 255)
|
||||
continue;
|
||||
var vector = new float[FEATURES_LENGTH];
|
||||
foreach (var (i, image) in images.Enumerate())
|
||||
{
|
||||
vector[i * (CHANNELS_COUNT + 3) + 0] = (float)image.visible;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 1] = (float)image.uv365;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 2] = (float)image.uv254;
|
||||
var color = image.mat.At<Vec3b>(row, col);
|
||||
vector[i * (CHANNELS_COUNT + 3) + 3] = color.Item0 / 255.0f;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 4] = color.Item1 / 255.0f;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 5] = color.Item2 / 255.0f;
|
||||
}
|
||||
var prediction = _regressionEngine?.Predict(new RegressionData
|
||||
{
|
||||
Value = -1,
|
||||
Features = vector
|
||||
});
|
||||
if (prediction is null)
|
||||
continue;
|
||||
result.Set<float>(row, col, prediction.Value);
|
||||
values[row * ANALYSIS_IMAGE_SIZE + col] = prediction.Value;
|
||||
}
|
||||
|
||||
var oldMean = Cv2.Mean(result, mask).Val0;
|
||||
values.Sort();
|
||||
float p999 = values[(int)(values.Length * 0.999)];
|
||||
Cv2.Threshold(result, result, p999, p999, ThresholdTypes.Trunc);
|
||||
var newMean = Cv2.Mean(result, mask).Val0;
|
||||
var scale = oldMean / newMean;
|
||||
Cv2.ConvertScaleAbs(result, result, scale);
|
||||
return (avgImage, roi, mask, contours, result, separatorType, sampleBrand);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Исключение при проведении анализа");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private async Task<(Mat avgImage, Rect roi, Mat mask, IEnumerable<Point[]> contours, string separatorType, string sampleBrand)?> Segment(IEnumerable<ImageData> imagesData)
|
||||
{
|
||||
var separatorPrefix = "SEPARATOR";
|
||||
var samplePrefix = "SAMPLE";
|
||||
|
||||
if (imagesData.Count() != IMAGES_COUNT)
|
||||
return null;
|
||||
|
||||
var avgImage = CalculateAverageImage(imagesData.Select(i => i.Path));
|
||||
if (avgImage is null)
|
||||
return null;
|
||||
|
||||
var roi = CalculateSeparatorRoi(avgImage);
|
||||
if (roi is null)
|
||||
{
|
||||
avgImage.Release();
|
||||
return null;
|
||||
}
|
||||
|
||||
avgImage = new Mat(avgImage, roi.Value);
|
||||
Cv2.Resize(avgImage, avgImage, new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE));
|
||||
|
||||
var images = new List<(Mat mat, double visible, double uv365, double uv254)>();
|
||||
foreach (var imageData in imagesData
|
||||
.OrderBy(i => i.VisibleIntensity)
|
||||
.ThenBy(i => i.Uv365Intensity)
|
||||
.ThenBy(i => i.Uv254Intensity))
|
||||
images.Add((
|
||||
LoadAndPrepare(imageData.Path, roi.Value, ANALYSIS_IMAGE_SIZE),
|
||||
imageData.VisibleIntensity,
|
||||
imageData.Uv365Intensity,
|
||||
imageData.Uv254Intensity
|
||||
));
|
||||
|
||||
var separatorMask = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_8UC1, new Scalar(0));
|
||||
Cv2.Circle(separatorMask, ANALYSIS_IMAGE_SIZE / 2, ANALYSIS_IMAGE_SIZE / 2, ANALYSIS_IMAGE_SIZE / 2, new Scalar(255), -1);
|
||||
|
||||
var result = new Mat(new Size(ANALYSIS_IMAGE_SIZE, ANALYSIS_IMAGE_SIZE), MatType.CV_16UC1, new Scalar(0));
|
||||
var keyMap = new Dictionary<string, ushort>();
|
||||
ushort lastKey = 0;
|
||||
|
||||
for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++)
|
||||
for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++)
|
||||
{
|
||||
if (separatorMask.At<byte>(row, col) != 255)
|
||||
continue;
|
||||
var vector = new float[FEATURES_LENGTH];
|
||||
foreach (var (i, image) in images.Enumerate())
|
||||
{
|
||||
vector[i * (CHANNELS_COUNT + 3) + 0] = (float)image.visible;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 1] = (float)image.uv365;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 2] = (float)image.uv254;
|
||||
var color = image.mat.At<Vec3b>(row, col);
|
||||
vector[i * (CHANNELS_COUNT + 3) + 3] = color.Item0 / 255.0f;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 4] = color.Item1 / 255.0f;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 5] = color.Item2 / 255.0f;
|
||||
}
|
||||
var prediction = _clusterizationEngine?.Predict(new ClusterizationData
|
||||
{
|
||||
Label = "",
|
||||
Features = vector
|
||||
});
|
||||
if (prediction is null)
|
||||
continue;
|
||||
if (!keyMap.ContainsKey(prediction.Label))
|
||||
keyMap.Add(prediction.Label, ++lastKey);
|
||||
result.Set<ushort>(row, col, keyMap[prediction.Label]);
|
||||
}
|
||||
|
||||
foreach (var image in images)
|
||||
image.mat.Release();
|
||||
|
||||
var valueMap = keyMap.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
var frequency = new Dictionary<string, int>();
|
||||
|
||||
|
||||
for (var row = 0; row < ANALYSIS_IMAGE_SIZE; row++)
|
||||
for (var col = 0; col < ANALYSIS_IMAGE_SIZE; col++)
|
||||
{
|
||||
var value = result.At<ushort>(row, col);
|
||||
if (value == 0)
|
||||
continue;
|
||||
|
||||
if (!frequency.ContainsKey(valueMap[value]))
|
||||
frequency.Add(valueMap[value], 0);
|
||||
frequency[valueMap[value]] += 1;
|
||||
if (valueMap[value].StartsWith(separatorPrefix))
|
||||
result.Set<ushort>(row, col, 0);
|
||||
if (valueMap[value].StartsWith(samplePrefix))
|
||||
result.Set<ushort>(row, col, ushort.MaxValue);
|
||||
}
|
||||
|
||||
result.ConvertTo(result, MatType.CV_8UC1, 1 / 255.0);
|
||||
|
||||
Cv2.MorphologyEx(result, result, MorphTypes.Close, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)), iterations: 2);
|
||||
Cv2.MorphologyEx(result, result, MorphTypes.Open, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)), iterations: 2);
|
||||
Cv2.Dilate(result, result, Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3)));
|
||||
Cv2.GaussianBlur(result, result, new Size(5, 5), 1);
|
||||
Cv2.Threshold(result, result, 127, 255, ThresholdTypes.Binary);
|
||||
|
||||
Cv2.FindContours(result, out var contours, out _, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
public static (Mat avgImage, Rect roi, List<float[]> vectors)? GetFeatureVectors(IEnumerable<ImageData> imagesData)
|
||||
{
|
||||
if (imagesData.Count() != IMAGES_COUNT)
|
||||
return null;
|
||||
|
||||
var avgImage = CalculateAverageImage(imagesData.Select(i => i.Path));
|
||||
if (avgImage is null)
|
||||
return null;
|
||||
|
||||
var roi = CalculateSeparatorRoi(avgImage);
|
||||
if (roi is null)
|
||||
return null;
|
||||
|
||||
var images = new List<(Mat mat, double visible, double uv365, double uv254)>();
|
||||
foreach (var imageData in imagesData
|
||||
.OrderBy(i => i.VisibleIntensity)
|
||||
.ThenBy(i => i.Uv365Intensity)
|
||||
.ThenBy(i => i.Uv254Intensity))
|
||||
images.Add((
|
||||
LoadAndPrepare(imageData.Path, roi.Value),
|
||||
imageData.VisibleIntensity,
|
||||
imageData.Uv365Intensity,
|
||||
imageData.Uv254Intensity
|
||||
));
|
||||
|
||||
var separatorMask = new Mat(new Size(EFFECTIVE_IMAGE_SIZE, EFFECTIVE_IMAGE_SIZE), MatType.CV_8UC1, new Scalar(0));
|
||||
Cv2.Circle(separatorMask, EFFECTIVE_IMAGE_SIZE / 2, EFFECTIVE_IMAGE_SIZE / 2, EFFECTIVE_IMAGE_SIZE / 2, new Scalar(255), -1);
|
||||
|
||||
var vectors = new List<float[]>();
|
||||
|
||||
for (var row = 0; row < EFFECTIVE_IMAGE_SIZE; row++)
|
||||
for (var col = 0; col < EFFECTIVE_IMAGE_SIZE; col++)
|
||||
{
|
||||
if (separatorMask.At<byte>(row, col) != 255)
|
||||
continue;
|
||||
var vector = new float[FEATURES_LENGTH];
|
||||
|
||||
foreach (var (i, image) in images.Enumerate())
|
||||
{
|
||||
vector[i * (CHANNELS_COUNT + 3) + 0] = (float)image.visible;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 1] = (float)image.uv365;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 2] = (float)image.uv254;
|
||||
|
||||
var color = image.mat.At<Vec3b>(row, col).ToVec3f() / 255.0f;
|
||||
|
||||
vector[i * (CHANNELS_COUNT + 3) + 3] = color.Item0;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 4] = color.Item1;
|
||||
vector[i * (CHANNELS_COUNT + 3) + 5] = color.Item2;
|
||||
}
|
||||
|
||||
vectors.Add(vector);
|
||||
}
|
||||
|
||||
foreach (var (mat, _, _, _) in images)
|
||||
mat.Release();
|
||||
|
||||
return (avgImage, roi.Value, vectors);
|
||||
}
|
||||
private static Mat LoadAndPrepare(string imagePath, Rect roi, int size = EFFECTIVE_IMAGE_SIZE)
|
||||
{
|
||||
var image = Cv2.ImRead(imagePath);
|
||||
image = new Mat(image, roi);
|
||||
Cv2.GaussianBlur(image, image, new Size(5, 5), 1);
|
||||
Cv2.Resize(image, image, new Size(size, size));
|
||||
return image;
|
||||
}
|
||||
public static Mat? CalculateAverageImage(IEnumerable<string> imagePaths)
|
||||
{
|
||||
if (imagePaths.Count() == 0)
|
||||
return null;
|
||||
|
||||
Mat avgImage = new Mat();
|
||||
|
||||
foreach (var imagePath in imagePaths)
|
||||
{
|
||||
var image = Cv2.ImRead(imagePath);
|
||||
|
||||
if (image.Channels() != 3)
|
||||
return null;
|
||||
|
||||
if (avgImage.Empty())
|
||||
avgImage = new Mat(image.Size(), MatType.CV_16UC3, new Scalar(0, 0, 0));
|
||||
|
||||
if (image.Size().Width != avgImage.Size().Width ||
|
||||
image.Size().Height != avgImage.Size().Height)
|
||||
return null;
|
||||
|
||||
Cv2.Add(image, avgImage, avgImage, dtype: MatType.CV_16U);
|
||||
|
||||
image.Release();
|
||||
}
|
||||
|
||||
avgImage.ConvertTo(avgImage, MatType.CV_8UC(avgImage.Channels()), 1f / imagePaths.Count());
|
||||
|
||||
return avgImage;
|
||||
}
|
||||
public static Rect? CalculateSeparatorRoi(Mat avgImage)
|
||||
{
|
||||
if (avgImage.Empty())
|
||||
return null;
|
||||
return new Rect(SEPARATOR_X, SEPARATOR_Y, SEPARATOR_WIDTH, SEPARATOR_HEIGHT);
|
||||
}
|
||||
// Этот метод конечно хорош, но проблема с тем, что при калибровке на чёрном продукте
|
||||
// он выдаёт очень обрезает часть полезного изображения из-за неравномерности освещения
|
||||
// public static Rect? CalculateSeparatorRoi(Mat avgImage)
|
||||
// {
|
||||
// if (avgImage.Empty())
|
||||
// return null;
|
||||
|
||||
// Mat avgImageClone = new Mat();
|
||||
// Mat mask = new Mat();
|
||||
// Rect? roi = null;
|
||||
|
||||
// avgImage.CopyTo(avgImageClone);
|
||||
|
||||
// avgImageClone = avgImageClone.CvtColor(ColorConversionCodes.BGR2GRAY);
|
||||
// Cv2.Threshold(avgImageClone, mask, SEPARATOR_ROI_THRESHOLD, 255, ThresholdTypes.Binary);
|
||||
// Cv2.FindContours(mask, out var contours, out _, RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
// var maxContour = contours.MaxBy(c => Cv2.ContourArea(c));
|
||||
// if (maxContour is null)
|
||||
// return null;
|
||||
|
||||
// roi = Cv2.BoundingRect(maxContour);
|
||||
|
||||
// avgImageClone.Release();
|
||||
// mask.Release();
|
||||
|
||||
// return roi;
|
||||
// }
|
||||
private static bool ContourIsValid(Point[] contour)
|
||||
{
|
||||
var bbox = Cv2.BoundingRect(contour);
|
||||
var area = Cv2.ContourArea(contour);
|
||||
return CONTOUR_MIN_SIZE < bbox.Width && CONTOUR_MIN_SIZE < bbox.Height && CONTOUR_MIN_AREA < area &&
|
||||
CONTOUR_MAX_SIZE > bbox.Width && CONTOUR_MAX_SIZE > bbox.Height && CONTOUR_MAX_AREA > area;
|
||||
}
|
||||
}
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public class AmineContentCalibrationContext : DbContext
|
||||
{
|
||||
public DbSet<ImageRecord> ImageRecords => Set<ImageRecord>();
|
||||
public DbSet<SeparatorRecord> SeparatorRecords => Set<SeparatorRecord>();
|
||||
public DbSet<CalibrationRecord> CalibrationRecords => Set<CalibrationRecord>();
|
||||
|
||||
public AmineContentCalibrationContext(DbContextOptions<AmineContentCalibrationContext> options)
|
||||
: base(options)
|
||||
{ }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<ImageRecord>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.Property(x => x.ImagePath)
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SeparatorRecord>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.HasMany(x => x.Images)
|
||||
.WithOne(x => x.UsedInSeparatorRecord)
|
||||
.HasForeignKey(x => x.UsedInSeparatorRecordId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.Property(x => x.Type)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256);
|
||||
entity.Property(x => x.Batch)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CalibrationRecord>(entity =>
|
||||
{
|
||||
entity.HasKey(x => x.Id);
|
||||
entity.HasMany(x => x.SampleImages)
|
||||
.WithOne(x => x.UsedInCalibrationRecordSample)
|
||||
.HasForeignKey(x => x.UsedInCalibrationRecordSampleId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasMany(x => x.SeparatorImages)
|
||||
.WithMany(x => x.UsedInCalibrationRecordsSeparator)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"CalibrationSeparatorImage",
|
||||
j => j
|
||||
.HasOne<ImageRecord>()
|
||||
.WithMany()
|
||||
.HasForeignKey("ImageRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j => j
|
||||
.HasOne<CalibrationRecord>()
|
||||
.WithMany()
|
||||
.HasForeignKey("CalibrationRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade),
|
||||
j =>
|
||||
{
|
||||
j.HasKey("CalibrationRecordId", "ImageRecordId");
|
||||
j.ToTable("CalibrationSeparatorImages");
|
||||
});
|
||||
entity.Property(x => x.SampleName)
|
||||
.HasMaxLength(512);
|
||||
entity.Property(x => x.SampleComment)
|
||||
.HasMaxLength(2048);
|
||||
entity.Property(x => x.SampleBrand)
|
||||
.HasMaxLength(512);
|
||||
entity.Property(x => x.SamplingPlace)
|
||||
.HasMaxLength(512);
|
||||
entity.Property(x => x.MixtureName)
|
||||
.HasMaxLength(512);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public class CalibrationContext : DbContext
|
||||
{
|
||||
public DbSet<SeparatorRecord> SeparatorRecords => Set<SeparatorRecord>();
|
||||
public DbSet<SampleRecord> SampleRecords => Set<SampleRecord>();
|
||||
public DbSet<VectorRecord> VectorRecords => Set<VectorRecord>();
|
||||
|
||||
public CalibrationContext(DbContextOptions<CalibrationContext> options)
|
||||
: base(options)
|
||||
{ }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<SeparatorRecord>()
|
||||
.HasMany(x => x.VectorRecords)
|
||||
.WithOne(x => x.SeparatorRecord)
|
||||
.HasForeignKey(x => x.SeparatorRecordId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<SampleRecord>()
|
||||
.HasMany(x => x.VectorRecords)
|
||||
.WithOne(x => x.SampleRecord)
|
||||
.HasForeignKey(x => x.SampleRecordId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -3,16 +3,16 @@ using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public class AmineContentCalibrationContextFactory : IDesignTimeDbContextFactory<AmineContentCalibrationContext>
|
||||
public class CalibrationContextFactory : IDesignTimeDbContextFactory<CalibrationContext>
|
||||
{
|
||||
public AmineContentCalibrationContext CreateDbContext(string[] args)
|
||||
public CalibrationContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AmineContentCalibrationContext>();
|
||||
var optionsBuilder = new DbContextOptionsBuilder<CalibrationContext>();
|
||||
|
||||
// Строка подключения для миграций в design-time
|
||||
// в runtime должна использоваться настоящая ConnectionString
|
||||
// в runtime должна использоваться настоящая ConnectionString
|
||||
optionsBuilder.UseSqlite("Data Source=AmineContentCalibration.db");
|
||||
|
||||
return new AmineContentCalibrationContext(optionsBuilder.Options);
|
||||
return new CalibrationContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public record ImageRecord
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Интенсивность видимого света (0..1)")]
|
||||
public required double VisibleIntensity { get; set; }
|
||||
[Comment("Интенсивность УФ излучения 365 нм (0..1)")]
|
||||
public required double Uv365Intensity { get; set; }
|
||||
[Comment("Интенсивность УФ излучения 254 нм (0..1)")]
|
||||
public required double Uv254Intensity { get; set; }
|
||||
[Comment("Путь к файлу изображения")]
|
||||
public required string ImagePath { get; set; }
|
||||
|
||||
// 1:N
|
||||
public int? UsedInSeparatorRecordId { get; set; }
|
||||
public SeparatorRecord? UsedInSeparatorRecord { get; set; }
|
||||
|
||||
public int? UsedInCalibrationRecordSampleId { get; set; }
|
||||
public CalibrationRecord? UsedInCalibrationRecordSample { get; set; }
|
||||
|
||||
// M:N
|
||||
public List<CalibrationRecord> UsedInCalibrationRecordsSeparator { get; set; } = new();
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AmineContentCalibration0 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CalibrationRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SampleName = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true, comment: "Название пробы"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", maxLength: 2048, nullable: true, comment: "Дополнительная информация о пробе"),
|
||||
SampleBrand = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true, comment: "Марка пробы"),
|
||||
SampleMass = table.Column<double>(type: "REAL", nullable: true, comment: "Масса пробы (кг)"),
|
||||
SamplingDateTime = table.Column<DateTime>(type: "TEXT", nullable: true, comment: "Дата и время отбора пробы"),
|
||||
SamplingPlace = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true, comment: "Место отбора пробы"),
|
||||
MixtureName = table.Column<string>(type: "TEXT", maxLength: 512, nullable: true, comment: "Название используемой кондиционирующей смеси"),
|
||||
MixtureAmineContent = table.Column<double>(type: "REAL", nullable: true, comment: "Содержание аминов в кондиционирующей смеси (%)"),
|
||||
MixtureNormalRate = table.Column<double>(type: "REAL", nullable: true, comment: "Норма расхода кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
MixtureActualRate = table.Column<double>(type: "REAL", nullable: true, comment: "Фактический расход кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
MeasuredContent = table.Column<double>(type: "REAL", nullable: true, comment: "Измеренное количество масла (кг/т | гр/кг)")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CalibrationRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SeparatorRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Тип сепаратора"),
|
||||
Batch = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Партия сепаратора")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SeparatorRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ImageRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
VisibleIntensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность видимого света (0..1)"),
|
||||
Uv365Intensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 365 нм (0..1)"),
|
||||
Uv254Intensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 254 нм (0..1)"),
|
||||
ImagePath = table.Column<string>(type: "TEXT", maxLength: 1024, nullable: false, comment: "Путь к файлу изображения"),
|
||||
UsedInSeparatorRecordId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
UsedInCalibrationRecordSampleId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ImageRecords", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ImageRecords_CalibrationRecords_UsedInCalibrationRecordSampleId",
|
||||
column: x => x.UsedInCalibrationRecordSampleId,
|
||||
principalTable: "CalibrationRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ImageRecords_SeparatorRecords_UsedInSeparatorRecordId",
|
||||
column: x => x.UsedInSeparatorRecordId,
|
||||
principalTable: "SeparatorRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CalibrationSeparatorImages",
|
||||
columns: table => new
|
||||
{
|
||||
CalibrationRecordId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ImageRecordId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CalibrationSeparatorImages", x => new { x.CalibrationRecordId, x.ImageRecordId });
|
||||
table.ForeignKey(
|
||||
name: "FK_CalibrationSeparatorImages_CalibrationRecords_CalibrationRecordId",
|
||||
column: x => x.CalibrationRecordId,
|
||||
principalTable: "CalibrationRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_CalibrationSeparatorImages_ImageRecords_ImageRecordId",
|
||||
column: x => x.ImageRecordId,
|
||||
principalTable: "ImageRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CalibrationSeparatorImages_ImageRecordId",
|
||||
table: "CalibrationSeparatorImages",
|
||||
column: "ImageRecordId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ImageRecords_UsedInCalibrationRecordSampleId",
|
||||
table: "ImageRecords",
|
||||
column: "UsedInCalibrationRecordSampleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ImageRecords_UsedInSeparatorRecordId",
|
||||
table: "ImageRecords",
|
||||
column: "UsedInSeparatorRecordId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CalibrationSeparatorImages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ImageRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CalibrationRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SeparatorRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
-94
@@ -10,9 +10,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
{
|
||||
[DbContext(typeof(AmineContentCalibrationContext))]
|
||||
[Migration("20260313083001_AmineContentCalibration0")]
|
||||
partial class AmineContentCalibration0
|
||||
[DbContext(typeof(CalibrationContext))]
|
||||
[Migration("20260428223237_Calibration0")]
|
||||
partial class Calibration0
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -20,120 +20,76 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("CalibrationSeparatorImage", b =>
|
||||
{
|
||||
b.Property<int>("CalibrationRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ImageRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CalibrationRecordId", "ImageRecordId");
|
||||
|
||||
b.HasIndex("ImageRecordId");
|
||||
|
||||
b.ToTable("CalibrationSeparatorImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<double?>("MeasuredContent")
|
||||
b.Property<string>("Image")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<double>("MeasuredContent")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Измеренное количество масла (кг/т | гр/кг)");
|
||||
|
||||
b.Property<double?>("MixtureActualRate")
|
||||
b.Property<double>("MixtureActualRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Фактический расход кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<double?>("MixtureAmineContent")
|
||||
b.Property<double>("MixtureAmineContent")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Содержание аминов в кондиционирующей смеси (%)");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название используемой кондиционирующей смеси");
|
||||
|
||||
b.Property<double?>("MixtureNormalRate")
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<string>("SampleBrand")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.HasMaxLength(2048)
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
b.Property<double?>("SampleMass")
|
||||
b.Property<double>("SampleMass")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Масса пробы (кг)");
|
||||
|
||||
b.Property<string>("SampleName")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название пробы");
|
||||
|
||||
b.Property<DateTime?>("SamplingDateTime")
|
||||
b.Property<DateTime>("SamplingDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время отбора пробы");
|
||||
|
||||
b.Property<string>("SamplingPlace")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Место отбора пробы");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CalibrationRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Путь к файлу изображения");
|
||||
|
||||
b.Property<int?>("UsedInCalibrationRecordSampleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("UsedInSeparatorRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Uv254Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 254 нм (0..1)");
|
||||
|
||||
b.Property<double>("Uv365Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 365 нм (0..1)");
|
||||
|
||||
b.Property<double>("VisibleIntensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность видимого света (0..1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UsedInCalibrationRecordSampleId");
|
||||
|
||||
b.HasIndex("UsedInSeparatorRecordId");
|
||||
|
||||
b.ToTable("ImageRecords");
|
||||
b.ToTable("SampleRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", b =>
|
||||
@@ -149,6 +105,12 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Партия сепаратора");
|
||||
|
||||
b.Property<string>("Image")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
@@ -160,46 +122,58 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
b.ToTable("SeparatorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CalibrationSeparatorImage", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.VectorRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("CalibrationRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ImageRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.Property<int?>("SampleRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("SeparatorRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.PrimitiveCollection<string>("Values")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения вектора");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SampleRecordId");
|
||||
|
||||
b.HasIndex("SeparatorRecordId");
|
||||
|
||||
b.ToTable("VectorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.VectorRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", "UsedInCalibrationRecordSample")
|
||||
.WithMany("SampleImages")
|
||||
.HasForeignKey("UsedInCalibrationRecordSampleId")
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", "SampleRecord")
|
||||
.WithMany("VectorRecords")
|
||||
.HasForeignKey("SampleRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", "UsedInSeparatorRecord")
|
||||
.WithMany("Images")
|
||||
.HasForeignKey("UsedInSeparatorRecordId")
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", "SeparatorRecord")
|
||||
.WithMany("VectorRecords")
|
||||
.HasForeignKey("SeparatorRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("UsedInCalibrationRecordSample");
|
||||
b.Navigation("SampleRecord");
|
||||
|
||||
b.Navigation("UsedInSeparatorRecord");
|
||||
b.Navigation("SeparatorRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", b =>
|
||||
{
|
||||
b.Navigation("SampleImages");
|
||||
b.Navigation("VectorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", b =>
|
||||
{
|
||||
b.Navigation("Images");
|
||||
b.Navigation("VectorRecords");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Calibration0 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SampleRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SampleName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название пробы"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", maxLength: 512, nullable: false, comment: "Дополнительная информация о пробе"),
|
||||
SampleBrand = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Марка пробы"),
|
||||
SampleMass = table.Column<double>(type: "REAL", nullable: false, comment: "Масса пробы (кг)"),
|
||||
SamplingDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время отбора пробы"),
|
||||
SamplingPlace = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Место отбора пробы"),
|
||||
MixtureName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название используемой кондиционирующей смеси"),
|
||||
MixtureAmineContent = table.Column<double>(type: "REAL", nullable: false, comment: "Содержание аминов в кондиционирующей смеси (%)"),
|
||||
MixtureNormalRate = table.Column<double>(type: "REAL", nullable: false, comment: "Норма расхода кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
MixtureActualRate = table.Column<double>(type: "REAL", nullable: false, comment: "Фактический расход кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
MeasuredContent = table.Column<double>(type: "REAL", nullable: false, comment: "Измеренное количество масла (кг/т | гр/кг)"),
|
||||
Image = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Изображение")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SampleRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SeparatorRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Тип сепаратора"),
|
||||
Batch = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Партия сепаратора"),
|
||||
Image = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Изображение")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SeparatorRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VectorRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SeparatorRecordId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
SampleRecordId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
Values = table.Column<string>(type: "TEXT", nullable: false, comment: "Значения вектора")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VectorRecords", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_VectorRecords_SampleRecords_SampleRecordId",
|
||||
column: x => x.SampleRecordId,
|
||||
principalTable: "SampleRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_VectorRecords_SeparatorRecords_SeparatorRecordId",
|
||||
column: x => x.SeparatorRecordId,
|
||||
principalTable: "SeparatorRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VectorRecords_SampleRecordId",
|
||||
table: "VectorRecords",
|
||||
column: "SampleRecordId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VectorRecords_SeparatorRecordId",
|
||||
table: "VectorRecords",
|
||||
column: "SeparatorRecordId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "VectorRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SampleRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SeparatorRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-93
@@ -9,128 +9,84 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
{
|
||||
[DbContext(typeof(AmineContentCalibrationContext))]
|
||||
partial class AmineContentCalibrationContextModelSnapshot : ModelSnapshot
|
||||
[DbContext(typeof(CalibrationContext))]
|
||||
partial class CalibrationContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("CalibrationSeparatorImage", b =>
|
||||
{
|
||||
b.Property<int>("CalibrationRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ImageRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CalibrationRecordId", "ImageRecordId");
|
||||
|
||||
b.HasIndex("ImageRecordId");
|
||||
|
||||
b.ToTable("CalibrationSeparatorImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<double?>("MeasuredContent")
|
||||
b.Property<string>("Image")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<double>("MeasuredContent")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Измеренное количество масла (кг/т | гр/кг)");
|
||||
|
||||
b.Property<double?>("MixtureActualRate")
|
||||
b.Property<double>("MixtureActualRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Фактический расход кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<double?>("MixtureAmineContent")
|
||||
b.Property<double>("MixtureAmineContent")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Содержание аминов в кондиционирующей смеси (%)");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название используемой кондиционирующей смеси");
|
||||
|
||||
b.Property<double?>("MixtureNormalRate")
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<string>("SampleBrand")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.HasMaxLength(2048)
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
b.Property<double?>("SampleMass")
|
||||
b.Property<double>("SampleMass")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Масса пробы (кг)");
|
||||
|
||||
b.Property<string>("SampleName")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название пробы");
|
||||
|
||||
b.Property<DateTime?>("SamplingDateTime")
|
||||
b.Property<DateTime>("SamplingDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время отбора пробы");
|
||||
|
||||
b.Property<string>("SamplingPlace")
|
||||
.HasMaxLength(512)
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Место отбора пробы");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CalibrationRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Путь к файлу изображения");
|
||||
|
||||
b.Property<int?>("UsedInCalibrationRecordSampleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("UsedInSeparatorRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Uv254Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 254 нм (0..1)");
|
||||
|
||||
b.Property<double>("Uv365Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 365 нм (0..1)");
|
||||
|
||||
b.Property<double>("VisibleIntensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность видимого света (0..1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UsedInCalibrationRecordSampleId");
|
||||
|
||||
b.HasIndex("UsedInSeparatorRecordId");
|
||||
|
||||
b.ToTable("ImageRecords");
|
||||
b.ToTable("SampleRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", b =>
|
||||
@@ -146,6 +102,12 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Партия сепаратора");
|
||||
|
||||
b.Property<string>("Image")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
@@ -157,46 +119,58 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Calibration.Migrations
|
||||
b.ToTable("SeparatorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CalibrationSeparatorImage", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.VectorRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("CalibrationRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ImageRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.Property<int?>("SampleRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("SeparatorRecordId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.PrimitiveCollection<string>("Values")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения вектора");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SampleRecordId");
|
||||
|
||||
b.HasIndex("SeparatorRecordId");
|
||||
|
||||
b.ToTable("VectorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.ImageRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.VectorRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", "UsedInCalibrationRecordSample")
|
||||
.WithMany("SampleImages")
|
||||
.HasForeignKey("UsedInCalibrationRecordSampleId")
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", "SampleRecord")
|
||||
.WithMany("VectorRecords")
|
||||
.HasForeignKey("SampleRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", "UsedInSeparatorRecord")
|
||||
.WithMany("Images")
|
||||
.HasForeignKey("UsedInSeparatorRecordId")
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", "SeparatorRecord")
|
||||
.WithMany("VectorRecords")
|
||||
.HasForeignKey("SeparatorRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("UsedInCalibrationRecordSample");
|
||||
b.Navigation("SampleRecord");
|
||||
|
||||
b.Navigation("UsedInSeparatorRecord");
|
||||
b.Navigation("SeparatorRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.CalibrationRecord", b =>
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SampleRecord", b =>
|
||||
{
|
||||
b.Navigation("SampleImages");
|
||||
b.Navigation("VectorRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Calibration.SeparatorRecord", b =>
|
||||
{
|
||||
b.Navigation("Images");
|
||||
b.Navigation("VectorRecords");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
+24
-16
@@ -1,35 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public record CalibrationRecord
|
||||
public record SampleRecord
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Название пробы")]
|
||||
public required string? SampleName { get; set; }
|
||||
[MaxLength(256)]
|
||||
public required string SampleName { get; set; }
|
||||
[Comment("Дополнительная информация о пробе")]
|
||||
public required string? SampleComment { get; set; }
|
||||
[MaxLength(512)]
|
||||
public required string SampleComment { get; set; }
|
||||
[Comment("Марка пробы")]
|
||||
public required string? SampleBrand { get; set; }
|
||||
[MaxLength(256)]
|
||||
public required string SampleBrand { get; set; }
|
||||
[Comment("Масса пробы (кг)")]
|
||||
public required double? SampleMass { get; set; }
|
||||
public required double SampleMass { get; set; }
|
||||
[Comment("Дата и время отбора пробы")]
|
||||
public required DateTime? SamplingDateTime { get; set; }
|
||||
public required DateTime SamplingDateTime { get; set; }
|
||||
[Comment("Место отбора пробы")]
|
||||
public required string? SamplingPlace { get; set; }
|
||||
[MaxLength(256)]
|
||||
public required string SamplingPlace { get; set; }
|
||||
[Comment("Название используемой кондиционирующей смеси")]
|
||||
public required string? MixtureName { get; set; }
|
||||
[MaxLength(256)]
|
||||
public required string MixtureName { get; set; }
|
||||
[Comment("Содержание аминов в кондиционирующей смеси (%)")]
|
||||
public required double? MixtureAmineContent { get; set; }
|
||||
public required double MixtureAmineContent { get; set; }
|
||||
[Comment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)")]
|
||||
public required double? MixtureNormalRate { get; set; }
|
||||
public required double MixtureNormalRate { get; set; }
|
||||
[Comment("Фактический расход кондиционирующей смеси (кг/т | гр/кг)")]
|
||||
public required double? MixtureActualRate { get; set; }
|
||||
public required double MixtureActualRate { get; set; }
|
||||
[Comment("Измеренное количество масла (кг/т | гр/кг)")]
|
||||
public required double? MeasuredContent { get; set; }
|
||||
[Comment("Изображения пробы")]
|
||||
public required List<ImageRecord> SampleImages { get; set; }
|
||||
[Comment("Изображения пустого сепаратора")]
|
||||
public required List<ImageRecord> SeparatorImages { get; set; }
|
||||
public required double MeasuredContent { get; set; }
|
||||
[Comment("Изображение")]
|
||||
[MaxLength(256)]
|
||||
public required string Image { get; set; }
|
||||
[Comment("Векторы")]
|
||||
public required List<VectorRecord> VectorRecords { get; set; } = new();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.IO.Pipelines;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -9,9 +9,14 @@ public record SeparatorRecord
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Тип сепаратора")]
|
||||
[MaxLength(256)]
|
||||
public required string Type { get; set; }
|
||||
[Comment("Партия сепаратора")]
|
||||
[MaxLength(256)]
|
||||
public required string Batch { get; set; }
|
||||
[Comment("Изображения сепаратора")]
|
||||
public required List<ImageRecord> Images { get; set; }
|
||||
[Comment("Изображение")]
|
||||
[MaxLength(256)]
|
||||
public required string Image { get; set; }
|
||||
[Comment("Векторы")]
|
||||
public required List<VectorRecord> VectorRecords { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Calibration;
|
||||
|
||||
public record VectorRecord
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Значения вектора")]
|
||||
public required float[] Values { get; set; }
|
||||
|
||||
public int? SeparatorRecordId { get; set; }
|
||||
public SeparatorRecord? SeparatorRecord { get; set; }
|
||||
|
||||
public int? SampleRecordId { get; set; }
|
||||
public SampleRecord? SampleRecord { get; set; }
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
|
||||
public class AmineContentResultsContext : DbContext
|
||||
{
|
||||
public class Record
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Название пробы")]
|
||||
public string SampleName { get; set; } = string.Empty;
|
||||
[Comment("Дополнительная информация о пробе")]
|
||||
public string SampleComment { get; set; } = string.Empty;
|
||||
[Comment("Марка пробы")]
|
||||
public string SampleBrand { get; set; } = string.Empty;
|
||||
[Comment("Масса пробы (кг)")]
|
||||
public double SampleMass { get; set; } = double.NaN;
|
||||
[Comment("Дата и время отбора пробы")]
|
||||
public DateTime SamplingDateTime { get; set; } = DateTime.Now;
|
||||
[Comment("Место отбора пробы")]
|
||||
public string SamplingPlace { get; set; } = string.Empty;
|
||||
[Comment("Изображения пробы")]
|
||||
public List<ImageData> SampleImages { get; set; } = new();
|
||||
[Comment("Изображения пустого сепаратора")]
|
||||
public List<ImageData> SeparatorImages { get; set; } = new();
|
||||
[Comment("Температура воздуха (℃)")]
|
||||
public double AirTemperature { get; set; } = new();
|
||||
[Comment("Относительная влажность воздуха (%)")]
|
||||
public double AirHumidity { get; set; } = new();
|
||||
}
|
||||
public class ImageData
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Интенсивность видимого света (0..1)")]
|
||||
public double VisibleIntensity { get; set; } = double.NaN;
|
||||
[Comment("Интенсивность УФ излучения 365 нм (0..1)")]
|
||||
public double Uv365Intensity { get; set; } = double.NaN;
|
||||
[Comment("Интенсивность УФ излучения 254 нм (0..1)")]
|
||||
public double Uv254Intensity { get; set; } = double.NaN;
|
||||
[Comment("Тип изображения (sample - проба или separator - сепаратор)")]
|
||||
public string ImageType { get; set; } = string.Empty;
|
||||
[Comment("Путь к файлу изображения")]
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
|
||||
[Comment("Записи в которых это изображение используется как изображение пробы")]
|
||||
public List<Record> SampleRecords { get; set; } = new();
|
||||
[Comment("Записи в которых это изображение используется как изображение сепаратора")]
|
||||
public List<Record> SeparatorRecords { get; set; } = new();
|
||||
}
|
||||
|
||||
public DbSet<Record> Records => Set<Record>();
|
||||
public DbSet<ImageData> Images => Set<ImageData>();
|
||||
|
||||
public AmineContentResultsContext(DbContextOptions<AmineContentResultsContext> options) : base(options) { }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Record>()
|
||||
.HasMany(r => r.SampleImages)
|
||||
.WithMany(i => i.SampleRecords)
|
||||
.UsingEntity(j => j.ToTable("SampleImages"));
|
||||
|
||||
modelBuilder.Entity<Record>()
|
||||
.HasMany(r => r.SeparatorImages)
|
||||
.WithMany(i => i.SeparatorRecords)
|
||||
.UsingEntity(j => j.ToTable("SeparatorImages"));
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
// <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(AmineContentResultsContext))]
|
||||
[Migration("20260313083009_AmineContentResults0")]
|
||||
partial class AmineContentResults0
|
||||
{
|
||||
/// <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.AmineContentResultsContext+ImageData", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Путь к файлу изображения");
|
||||
|
||||
b.Property<string>("ImageType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Тип изображения (sample - проба или separator - сепаратор)");
|
||||
|
||||
b.Property<double>("Uv254Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 254 нм (0..1)");
|
||||
|
||||
b.Property<double>("Uv365Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 365 нм (0..1)");
|
||||
|
||||
b.Property<double>("VisibleIntensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность видимого света (0..1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<double>("AirHumidity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Относительная влажность воздуха (%)");
|
||||
|
||||
b.Property<double>("AirTemperature")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Температура воздуха (℃)");
|
||||
|
||||
b.Property<string>("SampleBrand")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
b.Property<double>("SampleMass")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Масса пробы (кг)");
|
||||
|
||||
b.Property<string>("SampleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название пробы");
|
||||
|
||||
b.Property<DateTime>("SamplingDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время отбора пробы");
|
||||
|
||||
b.Property<string>("SamplingPlace")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Место отбора пробы");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Records");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord", b =>
|
||||
{
|
||||
b.Property<int>("SampleImagesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SampleRecordsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("SampleImagesId", "SampleRecordsId");
|
||||
|
||||
b.HasIndex("SampleRecordsId");
|
||||
|
||||
b.ToTable("SampleImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord1", b =>
|
||||
{
|
||||
b.Property<int>("SeparatorImagesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeparatorRecordsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("SeparatorImagesId", "SeparatorRecordsId");
|
||||
|
||||
b.HasIndex("SeparatorRecordsId");
|
||||
|
||||
b.ToTable("SeparatorImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+ImageData", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SampleImagesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SampleRecordsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord1", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+ImageData", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeparatorImagesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeparatorRecordsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AmineContentResults0 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Images",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
VisibleIntensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность видимого света (0..1)"),
|
||||
Uv365Intensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 365 нм (0..1)"),
|
||||
Uv254Intensity = table.Column<double>(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 254 нм (0..1)"),
|
||||
ImageType = table.Column<string>(type: "TEXT", nullable: false, comment: "Тип изображения (sample - проба или separator - сепаратор)"),
|
||||
ImagePath = table.Column<string>(type: "TEXT", nullable: false, comment: "Путь к файлу изображения")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Images", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Records",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SampleName = table.Column<string>(type: "TEXT", nullable: false, comment: "Название пробы"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", nullable: false, comment: "Дополнительная информация о пробе"),
|
||||
SampleBrand = table.Column<string>(type: "TEXT", nullable: false, comment: "Марка пробы"),
|
||||
SampleMass = table.Column<double>(type: "REAL", nullable: false, comment: "Масса пробы (кг)"),
|
||||
SamplingDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время отбора пробы"),
|
||||
SamplingPlace = table.Column<string>(type: "TEXT", nullable: false, comment: "Место отбора пробы"),
|
||||
AirTemperature = table.Column<double>(type: "REAL", nullable: false, comment: "Температура воздуха (℃)"),
|
||||
AirHumidity = table.Column<double>(type: "REAL", nullable: false, comment: "Относительная влажность воздуха (%)")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Records", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SampleImages",
|
||||
columns: table => new
|
||||
{
|
||||
SampleImagesId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SampleRecordsId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SampleImages", x => new { x.SampleImagesId, x.SampleRecordsId });
|
||||
table.ForeignKey(
|
||||
name: "FK_SampleImages_Images_SampleImagesId",
|
||||
column: x => x.SampleImagesId,
|
||||
principalTable: "Images",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SampleImages_Records_SampleRecordsId",
|
||||
column: x => x.SampleRecordsId,
|
||||
principalTable: "Records",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SeparatorImages",
|
||||
columns: table => new
|
||||
{
|
||||
SeparatorImagesId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SeparatorRecordsId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SeparatorImages", x => new { x.SeparatorImagesId, x.SeparatorRecordsId });
|
||||
table.ForeignKey(
|
||||
name: "FK_SeparatorImages_Images_SeparatorImagesId",
|
||||
column: x => x.SeparatorImagesId,
|
||||
principalTable: "Images",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_SeparatorImages_Records_SeparatorRecordsId",
|
||||
column: x => x.SeparatorRecordsId,
|
||||
principalTable: "Records",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SampleImages_SampleRecordsId",
|
||||
table: "SampleImages",
|
||||
column: "SampleRecordsId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SeparatorImages_SeparatorRecordsId",
|
||||
table: "SeparatorImages",
|
||||
column: "SeparatorRecordsId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SampleImages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SeparatorImages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Images");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Records");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+123
@@ -0,0 +1,123 @@
|
||||
// <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("20260429070705_Results0")]
|
||||
partial class Results0
|
||||
{
|
||||
/// <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>("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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Results0 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ResultRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SampleName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название пробы"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", maxLength: 512, nullable: false, comment: "Дополнительная информация о пробе"),
|
||||
SamplingDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время отбора пробы"),
|
||||
SamplingPlace = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Место отбора пробы"),
|
||||
Image = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Изображение"),
|
||||
SeparatorType = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Тип сепаратора"),
|
||||
SampleBrand = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Марка пробы"),
|
||||
MaskImage = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Маска"),
|
||||
ResultImage = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Изображение результата"),
|
||||
MinValue = table.Column<double>(type: "REAL", nullable: false, comment: "Минимальное значение"),
|
||||
MaxValue = table.Column<double>(type: "REAL", nullable: false, comment: "Максимальное значение"),
|
||||
ParticlesCount = table.Column<int>(type: "INTEGER", nullable: false, comment: "Количество частиц"),
|
||||
MeanValue = table.Column<double>(type: "REAL", nullable: false, comment: "Среднее значение"),
|
||||
MeanVariance = table.Column<double>(type: "REAL", nullable: false, comment: "Средняя дисперсия"),
|
||||
VarianceOfMean = table.Column<double>(type: "REAL", nullable: false, comment: "Дисперсия среднего"),
|
||||
MeanValues = table.Column<string>(type: "TEXT", nullable: false, comment: "Средние значения"),
|
||||
Variances = table.Column<string>(type: "TEXT", nullable: false, comment: "Дисперсии")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ResultRecords", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ResultRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
|
||||
{
|
||||
[DbContext(typeof(AmineContentResultsContext))]
|
||||
partial class AmineContentResultsContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+ImageData", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<string>("ImagePath")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Путь к файлу изображения");
|
||||
|
||||
b.Property<string>("ImageType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Тип изображения (sample - проба или separator - сепаратор)");
|
||||
|
||||
b.Property<double>("Uv254Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 254 нм (0..1)");
|
||||
|
||||
b.Property<double>("Uv365Intensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность УФ излучения 365 нм (0..1)");
|
||||
|
||||
b.Property<double>("VisibleIntensity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Интенсивность видимого света (0..1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.Property<double>("AirHumidity")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Относительная влажность воздуха (%)");
|
||||
|
||||
b.Property<double>("AirTemperature")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Температура воздуха (℃)");
|
||||
|
||||
b.Property<string>("SampleBrand")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
b.Property<double>("SampleMass")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Масса пробы (кг)");
|
||||
|
||||
b.Property<string>("SampleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название пробы");
|
||||
|
||||
b.Property<DateTime>("SamplingDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время отбора пробы");
|
||||
|
||||
b.Property<string>("SamplingPlace")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Место отбора пробы");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Records");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord", b =>
|
||||
{
|
||||
b.Property<int>("SampleImagesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SampleRecordsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("SampleImagesId", "SampleRecordsId");
|
||||
|
||||
b.HasIndex("SampleRecordsId");
|
||||
|
||||
b.ToTable("SampleImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord1", b =>
|
||||
{
|
||||
b.Property<int>("SeparatorImagesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeparatorRecordsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("SeparatorImagesId", "SeparatorRecordsId");
|
||||
|
||||
b.HasIndex("SeparatorRecordsId");
|
||||
|
||||
b.ToTable("SeparatorImages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+ImageData", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SampleImagesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SampleRecordsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ImageDataRecord1", b =>
|
||||
{
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+ImageData", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeparatorImagesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GSS2.Core.Analysis.AmineContent.Database.Results.AmineContentResultsContext+Record", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SeparatorRecordsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results.Migrations
|
||||
{
|
||||
[DbContext(typeof(ResultsContext))]
|
||||
partial class ResultsContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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>("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.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,53 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
|
||||
public class ResultRecord
|
||||
{
|
||||
[Comment("Уникальный идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Название пробы")]
|
||||
[MaxLength(256)]
|
||||
public required string SampleName { get; set; }
|
||||
[Comment("Дополнительная информация о пробе")]
|
||||
[MaxLength(512)]
|
||||
public required string SampleComment { get; set; }
|
||||
[Comment("Дата и время отбора пробы")]
|
||||
public required DateTime SamplingDateTime { get; set; }
|
||||
[Comment("Место отбора пробы")]
|
||||
[MaxLength(256)]
|
||||
public required string SamplingPlace { get; set; }
|
||||
[Comment("Изображение")]
|
||||
[MaxLength(256)]
|
||||
public required string Image { get; set; }
|
||||
[Comment("Тип сепаратора")]
|
||||
[MaxLength(256)]
|
||||
public required string SeparatorType { get; set; }
|
||||
[Comment("Марка пробы")]
|
||||
[MaxLength(256)]
|
||||
public required string SampleBrand { get; set; }
|
||||
[Comment("Маска")]
|
||||
[MaxLength(256)]
|
||||
public required string MaskImage { get; set; }
|
||||
[Comment("Изображение результата")]
|
||||
[MaxLength(256)]
|
||||
public required string ResultImage { get; set; }
|
||||
[Comment("Минимальное значение")]
|
||||
public required double MinValue { get; set; }
|
||||
[Comment("Максимальное значение")]
|
||||
public required double MaxValue { get; set; }
|
||||
[Comment("Количество частиц")]
|
||||
public required int ParticlesCount { get; set; }
|
||||
[Comment("Среднее значение")]
|
||||
public required double MeanValue { get; set; }
|
||||
[Comment("Средняя дисперсия")]
|
||||
public required double MeanVariance { get; set; }
|
||||
[Comment("Дисперсия среднего")]
|
||||
public required double VarianceOfMean { get; set; }
|
||||
[Comment("Средние значения")]
|
||||
public required double[] MeanValues { get; set; }
|
||||
[Comment("Дисперсии")]
|
||||
public required double[] Variances { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
|
||||
public class ResultsContext : DbContext
|
||||
{
|
||||
public DbSet<ResultRecord> ResultRecords => Set<ResultRecord>();
|
||||
|
||||
public ResultsContext(DbContextOptions<ResultsContext> options)
|
||||
: base(options)
|
||||
{ }
|
||||
}
|
||||
+5
-5
@@ -3,16 +3,16 @@ using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Results;
|
||||
|
||||
public class AmineContentResultsContextFactory : IDesignTimeDbContextFactory<AmineContentResultsContext>
|
||||
public class ResultsContextFactory : IDesignTimeDbContextFactory<ResultsContext>
|
||||
{
|
||||
public AmineContentResultsContext CreateDbContext(string[] args)
|
||||
public ResultsContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AmineContentResultsContext>();
|
||||
var optionsBuilder = new DbContextOptionsBuilder<ResultsContext>();
|
||||
|
||||
// Строка подключения для миграций в design-time
|
||||
// в runtime должна использоваться настоящая ConnectionString
|
||||
// в runtime должна использоваться настоящая ConnectionString
|
||||
optionsBuilder.UseSqlite("Data Source=AmineContentResults.db");
|
||||
|
||||
return new AmineContentResultsContext(optionsBuilder.Options);
|
||||
return new ResultsContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -11,7 +11,7 @@ using OpenCvSharp;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent;
|
||||
|
||||
public class AmineContentImageCapturerService
|
||||
public class ImageCapturerService
|
||||
{
|
||||
// Список интенсивностей освещения для каждого кадра
|
||||
public static readonly ReadOnlyCollection<(double visible, double uv365, double uv254)> IlluminatorIntensities =
|
||||
@@ -30,13 +30,13 @@ public class AmineContentImageCapturerService
|
||||
(0.00625, 1.0, 1.0)
|
||||
];
|
||||
|
||||
private readonly ILogger<AmineContentImageCapturerService> _logger;
|
||||
private readonly ILogger<ImageCapturerService> _logger;
|
||||
private readonly CameraService _camera;
|
||||
private readonly IlluminatorService _illuminator;
|
||||
private readonly LightsService _lights;
|
||||
|
||||
public AmineContentImageCapturerService(
|
||||
ILogger<AmineContentImageCapturerService> logger,
|
||||
public ImageCapturerService(
|
||||
ILogger<ImageCapturerService> logger,
|
||||
CameraService camera,
|
||||
IlluminatorService illuminator,
|
||||
LightsService lights
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace GSS2.Core.Analysis.AmineContent;
|
||||
|
||||
public record ImageData
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public double VisibleIntensity { get; set; }
|
||||
public double Uv365Intensity { get; set; }
|
||||
public double Uv254Intensity { get; set; }
|
||||
|
||||
public ImageData(string path, double visibleIntensity, double uv365Intensity, double uv254Intensity)
|
||||
{
|
||||
Path = path;
|
||||
VisibleIntensity = visibleIntensity;
|
||||
Uv365Intensity = uv365Intensity;
|
||||
Uv254Intensity = uv254Intensity;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user