forked from amkovkov/GranuSightSoftware2
comprehensive update
1) remove unused components: camera view (due it causes segmentation faults), compute resources and all related, illuminator controller (due in useless without camera view), image storage service, temperature and humidity service 2) database schema - reduce tables references, features storing in records themselves in compressed form, add records creation and editing date and time, add separator comment column 3) analysis - rework of pipeline and ui, now database storing only raw data and all display values calculated from it 4) lights service - add reconnection if disconnected 5) add width and height command line arguments 6) fix some typos and other issues
This commit is contained in:
@@ -84,6 +84,8 @@ public partial class AnalyzerService
|
||||
private PredictionEngine<RegressionData, RegressionPrediction>? _regressionEngine = null;
|
||||
|
||||
public bool Initialized { get; private set; } = false;
|
||||
public int InitializedBrandId { get; private set; } = 0;
|
||||
public int InitializedSeparatorId { get; private set; } = 0;
|
||||
|
||||
public AnalyzerService(ILogger<AnalyzerService> logger, Context Context)
|
||||
{
|
||||
@@ -93,6 +95,14 @@ public partial class AnalyzerService
|
||||
|
||||
public async Task Initialize(BrandRecord brand, SeparatorRecord separator, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Повторная инициализация если изменились калибровочные данные
|
||||
|
||||
if (Initialized &&
|
||||
InitializedBrandId == brand.Id &&
|
||||
InitializedSeparatorId == separator.Id
|
||||
)
|
||||
return;
|
||||
|
||||
Initialized = false;
|
||||
|
||||
_logger.LogInformation("Инициализация");
|
||||
@@ -116,6 +126,8 @@ public partial class AnalyzerService
|
||||
}
|
||||
|
||||
Initialized = true;
|
||||
InitializedBrandId = brand.Id;
|
||||
InitializedSeparatorId = separator.Id;
|
||||
}
|
||||
private async Task InitializeMlClusterizationEngine(BrandRecord brand, SeparatorRecord separator, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -126,35 +138,49 @@ public partial class AnalyzerService
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var sampleRecordIds = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id)
|
||||
.Select(r => r.Id);
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecordIds.Count());
|
||||
if (sampleRecordIds.Count() == 0)
|
||||
var sampleRecords = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id);
|
||||
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var separatorDataSet = _context.FeatureRecords
|
||||
.Where(r => r.SeparatorRecordId == separator.Id)
|
||||
.Select(r => new ClusterizationData
|
||||
if (separator.Features.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
if (separator.Features.Count() % FEATURES_LENGTH != 0)
|
||||
throw new InvalidDataException($"Неожидаемое количество калибровочных признаков для сепаратора: \"{separator.Type}\"");
|
||||
var separatorDataSet = separator.Features.Chunk(FEATURES_LENGTH)
|
||||
.Select(v => new ClusterizationData
|
||||
{
|
||||
Label = "separator",
|
||||
Features = r.Values.ToArray()
|
||||
});
|
||||
Features = v.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
|
||||
_logger.LogInformation("Загружено векторов для сепаратора: {}", separatorDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для сепаратора: \"{separator.Type}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var sampleDataSet = _context.FeatureRecords
|
||||
.Where(r => r.SeparatorRecordId != 0)
|
||||
.Where(r => sampleRecordIds.Contains(r.SampleRecordId))
|
||||
.Select(r => new ClusterizationData
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
{
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
}
|
||||
|
||||
var sampleDataSet = sampleRecords
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.SelectMany(r => r.Features.Chunk(FEATURES_LENGTH))
|
||||
.Select(f => new ClusterizationData
|
||||
{
|
||||
Label = "sample",
|
||||
Features = r.Values.ToArray()
|
||||
Features = f.ToArray()
|
||||
})
|
||||
.Shuffle();
|
||||
|
||||
_logger.LogInformation("Загружено векторов для пробы: {}", sampleDataSet.Count());
|
||||
if (separatorDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
@@ -187,32 +213,40 @@ public partial class AnalyzerService
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var sampleRecordIds = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id)
|
||||
.Select(r => r.Id);
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecordIds.Count());
|
||||
if (sampleRecordIds.Count() == 0)
|
||||
var sampleRecords = _context.SampleRecords
|
||||
.Where(r => r.BrandId == brand.Id);
|
||||
|
||||
_logger.LogInformation("Найдено калибровочных записей проб для марки: {}", sampleRecords.Count());
|
||||
if (sampleRecords.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных записей для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var sampleRecord in sampleRecords)
|
||||
{
|
||||
if (sampleRecord.Features.Count() == 0)
|
||||
_logger.LogError("Не найдено калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
if (sampleRecord.Features.Count() % FEATURES_LENGTH != 0)
|
||||
_logger.LogError("Неожидаемое количество калибровочных признаков для пробы: \"{}\"", sampleRecord.SampleName);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Подготовка данных");
|
||||
var trainDataSet = _context.FeatureRecords
|
||||
.Where(r => r.SeparatorRecordId != 0)
|
||||
.Where(r => sampleRecordIds.Contains(r.SampleRecordId))
|
||||
.ToList()
|
||||
.Select(r =>
|
||||
{
|
||||
var sample = _context.SampleRecords.First(s => s.Id == r.SampleRecordId);
|
||||
return new RegressionData
|
||||
{
|
||||
Value = (float)(
|
||||
sample.MeasuredContent >= 0 ? sample.MeasuredContent :
|
||||
sample.MixtureActualRate
|
||||
),
|
||||
Features = r.Values.ToArray()
|
||||
};
|
||||
})
|
||||
.Shuffle();
|
||||
var trainDataSet = sampleRecords
|
||||
.Where(r => r.Features.Count() > 0 && r.Features.Count() % FEATURES_LENGTH == 0)
|
||||
.Where(r => r.MeasuredContent >= 0 || r.MixtureActualRate >= 0)
|
||||
.SelectMany(r =>
|
||||
r.Features
|
||||
.Chunk(FEATURES_LENGTH)
|
||||
.Select(f =>
|
||||
new RegressionData
|
||||
{
|
||||
Value = (float)(r.MeasuredContent >= 0 ? r.MeasuredContent : r.MixtureActualRate),
|
||||
Features = f.ToArray()
|
||||
}
|
||||
)
|
||||
)
|
||||
.Shuffle()
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation("Загружено векторов для обучения: {}", trainDataSet.Count());
|
||||
if (trainDataSet.Count() == 0)
|
||||
throw new InvalidDataException($"Не найдено калибровочных данных для марки: \"{brand.BrandName} - {brand.MixtureName}\"");
|
||||
@@ -232,13 +266,15 @@ public partial class AnalyzerService
|
||||
Vector<double> meanVector = matrix.ColumnSums() / matrix.RowCount;
|
||||
Matrix<double> covarianceMatrix = MatrixCovariance(matrix, meanVector);
|
||||
Matrix<double> invCovarianceMatrix = covarianceMatrix.Inverse();
|
||||
trainDataSet = trainDataSet.Where((data, index) =>
|
||||
{
|
||||
var row = matrix.Row(index);
|
||||
double distance = CalculateMahalanobis(row, meanVector, invCovarianceMatrix);
|
||||
trainDataSet = trainDataSet
|
||||
.Where((data, index) =>
|
||||
{
|
||||
var row = matrix.Row(index);
|
||||
double distance = CalculateMahalanobis(row, meanVector, invCovarianceMatrix);
|
||||
|
||||
return distance <= FILTERING_THRESHOLD;
|
||||
}).ToList();
|
||||
return distance <= FILTERING_THRESHOLD;
|
||||
})
|
||||
.ToList();
|
||||
_logger.LogInformation("Векторов для обучения после фильтрации: {}", trainDataSet.Count());
|
||||
var trainData = _ml.Data.LoadFromEnumerable(trainDataSet);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -11,6 +11,10 @@ public class BrandRecord
|
||||
[Comment("Уникальный идентификатор")]
|
||||
[MaxLength(36)]
|
||||
public string Uuid { get; set; }
|
||||
[Comment("Дата и время создания")]
|
||||
public DateTime CreateDateTime { get; set; }
|
||||
[Comment("Дата и время редактирования")]
|
||||
public DateTime EditDateTime { get; set; }
|
||||
[Comment("Марка пробы")]
|
||||
[MaxLength(256)]
|
||||
public string BrandName { get; set; }
|
||||
@@ -19,25 +23,41 @@ public class BrandRecord
|
||||
public string MixtureName { get; set; }
|
||||
[Comment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)")]
|
||||
public double MixtureNormalRate { get; set; }
|
||||
[Comment("Дополнительная информация")]
|
||||
[MaxLength(1024)]
|
||||
public string Comment { get; set; }
|
||||
|
||||
public BrandRecord()
|
||||
: this(0, null, "", "", 0)
|
||||
: this(0, null, null, null, "", "", 0, "")
|
||||
{ }
|
||||
|
||||
public BrandRecord(
|
||||
int id = 0,
|
||||
string? uuid = null,
|
||||
DateTime? createDateTime = null,
|
||||
DateTime? editDateTime = null,
|
||||
string brandName = "",
|
||||
string mixtureName = "",
|
||||
double mixtureNormalRate = 0)
|
||||
double mixtureNormalRate = 0,
|
||||
string comment = ""
|
||||
)
|
||||
{
|
||||
Id = id;
|
||||
if (uuid is null)
|
||||
Uuid = Guid.NewGuid().ToString();
|
||||
else
|
||||
Uuid = uuid;
|
||||
if (createDateTime is null)
|
||||
CreateDateTime = DateTime.UtcNow;
|
||||
else
|
||||
CreateDateTime = createDateTime.Value;
|
||||
if (editDateTime is null)
|
||||
EditDateTime = DateTime.UtcNow;
|
||||
else
|
||||
EditDateTime = editDateTime.Value;
|
||||
BrandName = brandName;
|
||||
MixtureName = mixtureName;
|
||||
MixtureNormalRate = mixtureNormalRate;
|
||||
Comment = comment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,26 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database;
|
||||
|
||||
// TODO: Добавить внешние ключи в таблицы и правило что при удалении ключа ключ должен сбрасываться на 0
|
||||
public class Context : DbContext
|
||||
{
|
||||
public DbSet<BrandRecord> BrandRecords => Set<BrandRecord>();
|
||||
public DbSet<FeatureRecord> FeatureRecords => Set<FeatureRecord>();
|
||||
public DbSet<ImageRecord> ImageRecords => Set<ImageRecord>();
|
||||
// public DbSet<ResultRecord> ResultRecords => Set<ResultRecord>();
|
||||
public DbSet<ResultRecord> ResultRecords => Set<ResultRecord>();
|
||||
public DbSet<SeparatorRecord> SeparatorRecords => Set<SeparatorRecord>();
|
||||
public DbSet<SampleRecord> SampleRecords => Set<SampleRecord>();
|
||||
|
||||
public Context(DbContextOptions<Context> options)
|
||||
: base(options)
|
||||
{ }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<SeparatorRecord>()
|
||||
.Property(e => e.Features)
|
||||
.HasConversion(new CompressedFloatListConverter());
|
||||
modelBuilder.Entity<SampleRecord>()
|
||||
.Property(e => e.Features)
|
||||
.HasConversion(new CompressedFloatListConverter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database;
|
||||
|
||||
public class FeatureRecord
|
||||
{
|
||||
[Comment("Идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Значения вектора")]
|
||||
public List<float> Values { get; set; }
|
||||
[Comment("Сепаратор")]
|
||||
public int SeparatorRecordId { get; set; }
|
||||
[Comment("Проба")]
|
||||
public int SampleRecordId { get; set; }
|
||||
|
||||
public FeatureRecord()
|
||||
: this(0, null, 0, 0)
|
||||
{ }
|
||||
|
||||
public FeatureRecord(
|
||||
int id = 0,
|
||||
List<float>? values = null,
|
||||
int separatorRecordId = 0,
|
||||
int sampleRecordId = 0
|
||||
)
|
||||
{
|
||||
Id = id;
|
||||
if (values is null)
|
||||
Values = new List<float>();
|
||||
else
|
||||
Values = values;
|
||||
SeparatorRecordId = separatorRecordId;
|
||||
SampleRecordId = sampleRecordId;
|
||||
}
|
||||
}
|
||||
+159
-29
@@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("20260615113305_Migration0")]
|
||||
[Migration("20260624080642_Migration0")]
|
||||
partial class Migration0
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -33,6 +33,20 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
@@ -54,31 +68,6 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
b.ToTable("BrandRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.FeatureRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<int>("SampleRecordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Проба");
|
||||
|
||||
b.Property<int>("SeparatorRecordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Сепаратор");
|
||||
|
||||
b.PrimitiveCollection<string>("Values")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения вектора");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("FeatureRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ImageRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -96,6 +85,125 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
b.ToTable("ImageRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ResultRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("BrandName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<int>("MaskImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Маска");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название кондиционирующей смеси");
|
||||
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<int>("ParticlesCount")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Количество частиц");
|
||||
|
||||
b.Property<int>("ResultImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.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<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("XCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("XData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("YCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("YData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (данные)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ResultRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.SampleRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -107,7 +215,15 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Марка");
|
||||
|
||||
b.PrimitiveCollection<string>("FeatureIds")
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.PrimitiveCollection<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
@@ -126,7 +242,7 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
@@ -164,7 +280,21 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.PrimitiveCollection<string>("FeatureIds")
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.PrimitiveCollection<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
+47
-21
@@ -18,30 +18,18 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Uuid = table.Column<string>(type: "TEXT", maxLength: 36, nullable: false, comment: "Уникальный идентификатор"),
|
||||
CreateDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время создания"),
|
||||
EditDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время редактирования"),
|
||||
BrandName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Марка пробы"),
|
||||
MixtureName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название кондиционирующей смеси"),
|
||||
MixtureNormalRate = table.Column<double>(type: "REAL", nullable: false, comment: "Норма расхода кондиционирующей смеси (кг/т | гр/кг)")
|
||||
MixtureNormalRate = table.Column<double>(type: "REAL", nullable: false, comment: "Норма расхода кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
Comment = table.Column<string>(type: "TEXT", maxLength: 1024, nullable: false, comment: "Дополнительная информация")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BrandRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FeatureRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Values = table.Column<string>(type: "TEXT", nullable: false, comment: "Значения вектора"),
|
||||
SeparatorRecordId = table.Column<int>(type: "INTEGER", nullable: false, comment: "Сепаратор"),
|
||||
SampleRecordId = table.Column<int>(type: "INTEGER", nullable: false, comment: "Проба")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FeatureRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ImageRecords",
|
||||
columns: table => new
|
||||
@@ -55,6 +43,39 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
table.PrimaryKey("PK_ImageRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ResultRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Uuid = table.Column<string>(type: "TEXT", maxLength: 36, nullable: false, comment: "Уникальный идентификатор"),
|
||||
CreateDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время создания"),
|
||||
EditDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время редактирования"),
|
||||
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: "Место отбора пробы"),
|
||||
SeparatorType = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Тип сепаратора"),
|
||||
BrandName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Марка пробы"),
|
||||
MixtureName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название кондиционирующей смеси"),
|
||||
MixtureNormalRate = table.Column<double>(type: "REAL", nullable: false, comment: "Норма расхода кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
ImageId = table.Column<int>(type: "INTEGER", maxLength: 256, nullable: false, comment: "Изображение"),
|
||||
MaskImageId = table.Column<int>(type: "INTEGER", maxLength: 256, nullable: false, comment: "Маска"),
|
||||
ResultImageId = table.Column<int>(type: "INTEGER", maxLength: 256, nullable: false, comment: "Изображение результата"),
|
||||
ParticlesCount = table.Column<int>(type: "INTEGER", nullable: false, comment: "Количество частиц"),
|
||||
ValueCount = table.Column<string>(type: "TEXT", nullable: false, comment: "Значения частиц (количество)"),
|
||||
ValueData = table.Column<string>(type: "TEXT", nullable: false, comment: "Значения частиц (данные)"),
|
||||
XCount = table.Column<string>(type: "TEXT", nullable: false, comment: "Контура частиц, X координаты (количество)"),
|
||||
XData = table.Column<string>(type: "TEXT", nullable: false, comment: "Контура частиц, X координаты (данные)"),
|
||||
YCount = table.Column<string>(type: "TEXT", nullable: false, comment: "Контура частиц, Y координаты (количество)"),
|
||||
YData = table.Column<string>(type: "TEXT", nullable: false, comment: "Контура частиц, Y координаты (данные)")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ResultRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SampleRecords",
|
||||
columns: table => new
|
||||
@@ -62,15 +83,17 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Uuid = table.Column<string>(type: "TEXT", maxLength: 36, nullable: false, comment: "Уникальный идентификатор"),
|
||||
CreateDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время создания"),
|
||||
EditDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время редактирования"),
|
||||
SampleName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Название пробы"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", maxLength: 512, nullable: false, comment: "Дополнительная информация о пробе"),
|
||||
SampleComment = table.Column<string>(type: "TEXT", maxLength: 1024, nullable: false, comment: "Дополнительная информация о пробе"),
|
||||
SamplingDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время отбора пробы (utc)"),
|
||||
BrandId = table.Column<int>(type: "INTEGER", nullable: false, comment: "Марка"),
|
||||
SamplingPlace = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Место отбора пробы"),
|
||||
MixtureActualRate = table.Column<double>(type: "REAL", nullable: false, comment: "Фактический расход кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
MeasuredContent = table.Column<double>(type: "REAL", nullable: false, comment: "Измеренное количество кондиционирующей смеси (кг/т | гр/кг)"),
|
||||
ImageId = table.Column<int>(type: "INTEGER", nullable: false, comment: "Изображение"),
|
||||
FeatureIds = table.Column<string>(type: "TEXT", nullable: false, comment: "Признаки")
|
||||
Features = table.Column<string>(type: "TEXT", nullable: false, comment: "Признаки")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -84,9 +107,12 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false, comment: "Идентификатор")
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Uuid = table.Column<string>(type: "TEXT", maxLength: 36, nullable: false, comment: "Уникальный идентификатор"),
|
||||
CreateDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время создания"),
|
||||
EditDateTime = table.Column<DateTime>(type: "TEXT", nullable: false, comment: "Дата и время редактирования"),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false, comment: "Тип"),
|
||||
Comment = table.Column<string>(type: "TEXT", maxLength: 1024, nullable: false, comment: "Дополнительная информация"),
|
||||
ImageId = table.Column<int>(type: "INTEGER", nullable: false, comment: "Изображение"),
|
||||
FeatureIds = table.Column<string>(type: "TEXT", nullable: false, comment: "Признаки")
|
||||
Features = table.Column<string>(type: "TEXT", nullable: false, comment: "Признаки")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -101,10 +127,10 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
name: "BrandRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FeatureRecords");
|
||||
name: "ImageRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ImageRecords");
|
||||
name: "ResultRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SampleRecords");
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GSS2.Core.Analysis.AmineContent.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("20260624154152_Migration1")]
|
||||
partial class Migration1
|
||||
{
|
||||
/// <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.BrandRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("BrandName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название кондиционирующей смеси");
|
||||
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("BrandRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ImageRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("Base64")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения вектора");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ImageRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ResultRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("BrandName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<int>("MaskImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Маска");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название кондиционирующей смеси");
|
||||
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<int>("ParticlesCount")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Количество частиц");
|
||||
|
||||
b.Property<int>("ResultImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.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<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("XCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("XData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("YCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("YData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (данные)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ResultRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.SampleRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<int>("BrandId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Марка");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<double>("MeasuredContent")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Измеренное количество кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<double>("MixtureActualRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Фактический расход кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
b.Property<string>("SampleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название пробы");
|
||||
|
||||
b.Property<DateTime>("SamplingDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время отбора пробы (utc)");
|
||||
|
||||
b.Property<string>("SamplingPlace")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Место отбора пробы");
|
||||
|
||||
b.Property<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SampleRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.SeparatorRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Тип");
|
||||
|
||||
b.Property<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SeparatorRecords");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Migration1 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,20 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
@@ -51,31 +65,6 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
b.ToTable("BrandRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.FeatureRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<int>("SampleRecordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Проба");
|
||||
|
||||
b.Property<int>("SeparatorRecordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Сепаратор");
|
||||
|
||||
b.PrimitiveCollection<string>("Values")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения вектора");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("FeatureRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ImageRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -93,6 +82,125 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
b.ToTable("ImageRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.ResultRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.Property<string>("BrandName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Марка пробы");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<int>("ImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Изображение");
|
||||
|
||||
b.Property<int>("MaskImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Маска");
|
||||
|
||||
b.Property<string>("MixtureName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Название кондиционирующей смеси");
|
||||
|
||||
b.Property<double>("MixtureNormalRate")
|
||||
.HasColumnType("REAL")
|
||||
.HasComment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)");
|
||||
|
||||
b.Property<int>("ParticlesCount")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Количество частиц");
|
||||
|
||||
b.Property<int>("ResultImageId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("INTEGER")
|
||||
.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<string>("Uuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Уникальный идентификатор");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("ValueData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Значения частиц (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("XCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("XData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, X координаты (данные)");
|
||||
|
||||
b.PrimitiveCollection<string>("YCount")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (количество)");
|
||||
|
||||
b.PrimitiveCollection<string>("YData")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Контура частиц, Y координаты (данные)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ResultRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.SampleRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -104,7 +212,15 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Марка");
|
||||
|
||||
b.PrimitiveCollection<string>("FeatureIds")
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
@@ -123,7 +239,7 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
|
||||
b.Property<string>("SampleComment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация о пробе");
|
||||
|
||||
@@ -161,7 +277,21 @@ namespace GSS2.Core.Analysis.AmineContent.Database.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasComment("Идентификатор");
|
||||
|
||||
b.PrimitiveCollection<string>("FeatureIds")
|
||||
b.Property<string>("Comment")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дополнительная информация");
|
||||
|
||||
b.Property<DateTime>("CreateDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время создания");
|
||||
|
||||
b.Property<DateTime>("EditDateTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Дата и время редактирования");
|
||||
|
||||
b.Property<string>("Features")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasComment("Признаки");
|
||||
|
||||
@@ -1,60 +1,149 @@
|
||||
// using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
// namespace GSS2.Core.Analysis.AmineContent.Database;
|
||||
namespace GSS2.Core.Analysis.AmineContent.Database;
|
||||
|
||||
// public class ResultRecord
|
||||
// {
|
||||
// [Comment("Идентификатор")]
|
||||
// public int Id { get; set; }
|
||||
// [Comment("Уникальный идентификатор")]
|
||||
// [MaxLength(36)]
|
||||
// public required string Uuid { 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? MeanQuality { get; set; }
|
||||
// [Comment("Средние значения")]
|
||||
// public required double[] MeanValues { get; set; }
|
||||
// [Comment("Дисперсии")]
|
||||
// public required double[] Variances { get; set; }
|
||||
// [Comment("Качества обработки")]
|
||||
// public required double[]? Qualities { get; set; }
|
||||
// }
|
||||
public class ResultRecord
|
||||
{
|
||||
[Comment("Идентификатор")]
|
||||
public int Id { get; set; }
|
||||
[Comment("Уникальный идентификатор")]
|
||||
[MaxLength(36)]
|
||||
public string Uuid { get; set; }
|
||||
[Comment("Дата и время создания")]
|
||||
public DateTime CreateDateTime { get; set; }
|
||||
[Comment("Дата и время редактирования")]
|
||||
public DateTime EditDateTime { get; set; }
|
||||
[Comment("Название пробы")]
|
||||
[MaxLength(256)]
|
||||
public string SampleName { get; set; }
|
||||
[Comment("Дополнительная информация о пробе")]
|
||||
[MaxLength(512)]
|
||||
public string SampleComment { get; set; }
|
||||
[Comment("Дата и время отбора пробы")]
|
||||
public DateTime SamplingDateTime { get; set; }
|
||||
[Comment("Место отбора пробы")]
|
||||
[MaxLength(256)]
|
||||
public string SamplingPlace { get; set; }
|
||||
[Comment("Тип сепаратора")]
|
||||
[MaxLength(256)]
|
||||
public string SeparatorType { get; set; }
|
||||
[Comment("Марка пробы")]
|
||||
[MaxLength(256)]
|
||||
public string BrandName { get; set; }
|
||||
[Comment("Название кондиционирующей смеси")]
|
||||
[MaxLength(256)]
|
||||
public string MixtureName { get; set; }
|
||||
[Comment("Норма расхода кондиционирующей смеси (кг/т | гр/кг)")]
|
||||
public double MixtureNormalRate { get; set; }
|
||||
[Comment("Изображение")]
|
||||
[MaxLength(256)]
|
||||
public int ImageId { get; set; }
|
||||
[Comment("Маска")]
|
||||
[MaxLength(256)]
|
||||
public int MaskImageId { get; set; }
|
||||
[Comment("Изображение результата")]
|
||||
[MaxLength(256)]
|
||||
public int ResultImageId { get; set; }
|
||||
[Comment("Количество частиц")]
|
||||
public int ParticlesCount { get; set; }
|
||||
[Comment("Значения частиц (количество)")]
|
||||
public List<int> ValueCount { get; set; }
|
||||
[Comment("Значения частиц (данные)")]
|
||||
public List<float> ValueData { get; set; }
|
||||
[Comment("Контура частиц, X координаты (количество)")]
|
||||
public List<int> XCount { get; set; }
|
||||
[Comment("Контура частиц, X координаты (данные)")]
|
||||
public List<float> XData { get; set; }
|
||||
[Comment("Контура частиц, Y координаты (количество)")]
|
||||
public List<int> YCount { get; set; }
|
||||
[Comment("Контура частиц, Y координаты (данные)")]
|
||||
public List<float> YData { get; set; }
|
||||
|
||||
public ResultRecord()
|
||||
: this(0, null, null, null, "", "", null, "", "", "", "", -1, 0, 0, 0, -1, null, null, null)
|
||||
{ }
|
||||
public ResultRecord(
|
||||
int id = 0,
|
||||
string? uuid = null,
|
||||
DateTime? createDateTime = null,
|
||||
DateTime? editDateTime = null,
|
||||
string sampleName = "",
|
||||
string sampleComment = "",
|
||||
DateTime? samplingDateTime = null,
|
||||
string samplingPlace = "",
|
||||
string separatorType = "",
|
||||
string brandName = "",
|
||||
string mixtureName = "",
|
||||
double mixtureNormalRate = -1,
|
||||
int imageId = 0,
|
||||
int maskImageId = 0,
|
||||
int resultImageId = 0,
|
||||
int particlesCount = -1,
|
||||
List<float[]>? values = null,
|
||||
List<float[]>? xs = null,
|
||||
List<float[]>? ys = null
|
||||
)
|
||||
{
|
||||
Id = id;
|
||||
if (uuid is null)
|
||||
Uuid = Guid.NewGuid().ToString();
|
||||
else
|
||||
Uuid = uuid;
|
||||
if (createDateTime is null)
|
||||
CreateDateTime = DateTime.UtcNow;
|
||||
else
|
||||
CreateDateTime = createDateTime.Value;
|
||||
if (editDateTime is null)
|
||||
EditDateTime = DateTime.UtcNow;
|
||||
else
|
||||
EditDateTime = editDateTime.Value;
|
||||
SampleName = sampleName;
|
||||
SampleComment = sampleComment;
|
||||
if (samplingDateTime is null)
|
||||
SamplingDateTime = DateTime.UtcNow;
|
||||
else
|
||||
SamplingDateTime = samplingDateTime.Value;
|
||||
SamplingPlace = samplingPlace;
|
||||
SeparatorType = separatorType;
|
||||
BrandName = brandName;
|
||||
MixtureName = mixtureName;
|
||||
MixtureNormalRate = mixtureNormalRate;
|
||||
ImageId = imageId;
|
||||
MaskImageId = maskImageId;
|
||||
ResultImageId = resultImageId;
|
||||
ParticlesCount = particlesCount;
|
||||
if (values is null)
|
||||
{
|
||||
ValueCount = new List<int>();
|
||||
ValueData = new List<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ValueCount = values.Select(fs => fs.Count()).ToList();
|
||||
ValueData = values.SelectMany(fs => fs).ToList();
|
||||
}
|
||||
if (xs is null)
|
||||
{
|
||||
XCount = new List<int>();
|
||||
XData = new List<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
XCount = xs.Select(fs => fs.Count()).ToList();
|
||||
XData = xs.SelectMany(fs => fs).ToList();
|
||||
}
|
||||
if (ys is null)
|
||||
{
|
||||
YCount = new List<int>();
|
||||
YData = new List<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
YCount = ys.Select(fs => fs.Count()).ToList();
|
||||
YData = ys.SelectMany(fs => fs).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,15 @@ public class SampleRecord
|
||||
[Comment("Уникальный идентификатор")]
|
||||
[MaxLength(36)]
|
||||
public string Uuid { get; set; }
|
||||
[Comment("Дата и время создания")]
|
||||
public DateTime CreateDateTime { get; set; }
|
||||
[Comment("Дата и время редактирования")]
|
||||
public DateTime EditDateTime { get; set; }
|
||||
[Comment("Название пробы")]
|
||||
[MaxLength(256)]
|
||||
public string SampleName { get; set; }
|
||||
[Comment("Дополнительная информация о пробе")]
|
||||
[MaxLength(512)]
|
||||
[MaxLength(1024)]
|
||||
public string SampleComment { get; set; }
|
||||
[Comment("Дата и время отбора пробы (utc)")]
|
||||
public DateTime SamplingDateTime { get; set; }
|
||||
@@ -31,14 +35,16 @@ public class SampleRecord
|
||||
[Comment("Изображение")]
|
||||
public int ImageId { get; set; }
|
||||
[Comment("Признаки")]
|
||||
public List<int> FeatureIds { get; set; }
|
||||
public List<float> Features { get; set; }
|
||||
|
||||
public SampleRecord()
|
||||
: this(0, null, "", "", null, 0, "", -1, -1, 0, null)
|
||||
: this(0, null, null, null, "", "", null, 0, "", -1, -1, 0, null)
|
||||
{ }
|
||||
public SampleRecord(
|
||||
int id = 0,
|
||||
string? uuid = null,
|
||||
DateTime? createDateTime = null,
|
||||
DateTime? editDateTime = null,
|
||||
string sampleName = "",
|
||||
string sampleComment = "",
|
||||
DateTime? samplingDateTime = null,
|
||||
@@ -47,7 +53,7 @@ public class SampleRecord
|
||||
double mixtureActualRate = -1,
|
||||
double measuredContent = -1,
|
||||
int imageId = 0,
|
||||
List<int>? featureIds = null
|
||||
List<float[]>? features = null
|
||||
)
|
||||
{
|
||||
Id = id;
|
||||
@@ -55,6 +61,14 @@ public class SampleRecord
|
||||
Uuid = Guid.NewGuid().ToString();
|
||||
else
|
||||
Uuid = uuid;
|
||||
if (createDateTime is null)
|
||||
CreateDateTime = DateTime.UtcNow;
|
||||
else
|
||||
CreateDateTime = createDateTime.Value;
|
||||
if (editDateTime is null)
|
||||
EditDateTime = DateTime.UtcNow;
|
||||
else
|
||||
EditDateTime = editDateTime.Value;
|
||||
SampleName = sampleName;
|
||||
SampleComment = sampleComment;
|
||||
if (samplingDateTime is null)
|
||||
@@ -66,9 +80,9 @@ public class SampleRecord
|
||||
MixtureActualRate = mixtureActualRate;
|
||||
MeasuredContent = measuredContent;
|
||||
ImageId = imageId;
|
||||
if (featureIds is null)
|
||||
FeatureIds = new List<int>();
|
||||
if (features is null)
|
||||
Features = new List<float>();
|
||||
else
|
||||
FeatureIds = featureIds;
|
||||
Features = features.SelectMany(fs => fs).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,34 @@ public class SeparatorRecord
|
||||
[Comment("Уникальный идентификатор")]
|
||||
[MaxLength(36)]
|
||||
public string Uuid { get; set; }
|
||||
[Comment("Дата и время создания")]
|
||||
public DateTime CreateDateTime { get; set; }
|
||||
[Comment("Дата и время редактирования")]
|
||||
public DateTime EditDateTime { get; set; }
|
||||
[Comment("Тип")]
|
||||
[MaxLength(256)]
|
||||
public string Type { get; set; }
|
||||
[Comment("Дополнительная информация")]
|
||||
[MaxLength(1024)]
|
||||
public string Comment { get; set; }
|
||||
[Comment("Изображение")]
|
||||
public int ImageId { get; set; }
|
||||
[Comment("Признаки")]
|
||||
public List<int> FeatureIds { get; set; }
|
||||
public List<float> Features { get; set; }
|
||||
|
||||
public SeparatorRecord()
|
||||
: this(0, "", "", 0, null)
|
||||
: this(0, null, null, null, "", "", 0, null)
|
||||
{ }
|
||||
|
||||
public SeparatorRecord(
|
||||
int id = 0,
|
||||
string? uuid = null,
|
||||
DateTime? createDateTime = null,
|
||||
DateTime? editDateTime = null,
|
||||
string type = "",
|
||||
string comment = "",
|
||||
int imageId = 0,
|
||||
List<int>? featureIds = null
|
||||
List<float[]>? features = null
|
||||
)
|
||||
{
|
||||
Id = id;
|
||||
@@ -36,11 +46,20 @@ public class SeparatorRecord
|
||||
Uuid = Guid.NewGuid().ToString();
|
||||
else
|
||||
Uuid = uuid;
|
||||
Type = type;
|
||||
ImageId = imageId;
|
||||
if (featureIds is null)
|
||||
FeatureIds = new List<int>();
|
||||
if (createDateTime is null)
|
||||
CreateDateTime = DateTime.UtcNow;
|
||||
else
|
||||
FeatureIds = featureIds;
|
||||
CreateDateTime = createDateTime.Value;
|
||||
if (editDateTime is null)
|
||||
EditDateTime = DateTime.UtcNow;
|
||||
else
|
||||
EditDateTime = editDateTime.Value;
|
||||
Type = type;
|
||||
Comment = comment;
|
||||
ImageId = imageId;
|
||||
if (features is null)
|
||||
Features = new List<float>();
|
||||
else
|
||||
Features = features.SelectMany(fs => fs).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,6 @@ public class ImageCapturerService : IImageCapturerService
|
||||
// Если ViewFinder запущен, то для съёмки изображений его нужно остановить
|
||||
// потом после съёмки снова запустить
|
||||
var needRestartViewFinder = _camera.ViewFinderStarted;
|
||||
if (needRestartViewFinder)
|
||||
_camera.StopViewFinder();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -117,9 +115,6 @@ public class ImageCapturerService : IImageCapturerService
|
||||
{
|
||||
// В любом случае отключаем осветитель
|
||||
_illuminator.TurnOff();
|
||||
// Запускаем обратно ViewFinder, если он был запущен и ещё не запущен обратно
|
||||
if (needRestartViewFinder && !_camera.ViewFinderStarted)
|
||||
_camera.StartViewFinder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,22 +28,22 @@ public static class DependencyInjectionHelper
|
||||
logging.AddProvider(new FileLoggerProvider("last_run.log", false));
|
||||
return logging;
|
||||
}
|
||||
public static IServiceCollection AddTemperatureHumidityService(this IServiceCollection services, string section) => services
|
||||
.AddSingleton(
|
||||
serviceProvider =>
|
||||
{
|
||||
var configuration = serviceProvider.GetService<IConfiguration>();
|
||||
if (configuration is null)
|
||||
throw new Exception("Cannot get configuration");
|
||||
var serviceConfiguration = configuration.GetSection(section).Get<TemperatureHumidityService.TemperatureHumidityServiceConfiguration>();
|
||||
if (serviceConfiguration is null)
|
||||
throw new InvalidConfigurationException($"Cannot get configuration {section}");
|
||||
var logger = serviceProvider.GetService<ILogger<TemperatureHumidityService>>();
|
||||
if (logger is null)
|
||||
throw new Exception("Cannot get logger");
|
||||
return new TemperatureHumidityService(logger, serviceConfiguration);
|
||||
}
|
||||
);
|
||||
// public static IServiceCollection AddTemperatureHumidityService(this IServiceCollection services, string section) => services
|
||||
// .AddSingleton(
|
||||
// serviceProvider =>
|
||||
// {
|
||||
// var configuration = serviceProvider.GetService<IConfiguration>();
|
||||
// if (configuration is null)
|
||||
// throw new Exception("Cannot get configuration");
|
||||
// var serviceConfiguration = configuration.GetSection(section).Get<TemperatureHumidityService.TemperatureHumidityServiceConfiguration>();
|
||||
// if (serviceConfiguration is null)
|
||||
// throw new InvalidConfigurationException($"Cannot get configuration {section}");
|
||||
// var logger = serviceProvider.GetService<ILogger<TemperatureHumidityService>>();
|
||||
// if (logger is null)
|
||||
// throw new Exception("Cannot get logger");
|
||||
// return new TemperatureHumidityService(logger, serviceConfiguration);
|
||||
// }
|
||||
// );
|
||||
public static IServiceCollection AddLightsService(this IServiceCollection services, string section) => services
|
||||
.AddSingleton(
|
||||
serviceProvider =>
|
||||
@@ -92,21 +92,9 @@ public static class DependencyInjectionHelper
|
||||
return new CameraService(logger, serviceConfiguration, autoInitialize);
|
||||
}
|
||||
);
|
||||
public static IServiceCollection AddComputeResourcesService(this IServiceCollection services) => services
|
||||
.AddSingleton<ComputeResourcesService>();
|
||||
[Obsolete]
|
||||
public static IServiceCollection AddUsbService(this IServiceCollection services) => services
|
||||
.AddSingleton<UsbService>();
|
||||
public static IServiceCollection AddImageStorageService(this IServiceCollection services, DirectoryInfo rootDirectory) => services
|
||||
.AddSingleton<ImageStorageService>(
|
||||
serviceProvider =>
|
||||
{
|
||||
var logger = serviceProvider.GetService<ILogger<ImageStorageService>>();
|
||||
if (logger is null)
|
||||
throw new Exception("Cannot get logger");
|
||||
return new ImageStorageService(logger, rootDirectory);
|
||||
}
|
||||
);
|
||||
|
||||
public static IServiceCollection AddAmineContentContext(this IServiceCollection services, IConfigurationManager configuration) => services
|
||||
.AddDbContext<Context>(
|
||||
@@ -121,9 +109,9 @@ public static class DependencyInjectionHelper
|
||||
public static IServiceCollection AddAmineContentImageCapturerService(this IServiceCollection services, bool mock = false)
|
||||
{
|
||||
if (!mock)
|
||||
return services.AddSingleton<ImageCapturerService>();
|
||||
return services.AddSingleton<IImageCapturerService, ImageCapturerService>();
|
||||
|
||||
return services.AddSingleton<ImageCapturerMockService>(serviceProvider =>
|
||||
return services.AddSingleton<IImageCapturerService, ImageCapturerMockService>(serviceProvider =>
|
||||
{
|
||||
var logger = serviceProvider.GetService<ILogger<ImageCapturerMockService>>();
|
||||
if (logger is null)
|
||||
|
||||
@@ -13,4 +13,3 @@ public static class ConfigurationSectionExtensions
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,4 +46,32 @@ public static class EnumerableExtensions
|
||||
variance += Math.Pow(value - mean, 2.0);
|
||||
return variance / values.Count();
|
||||
}
|
||||
public static double StdDev(this IEnumerable<double> values) => Math.Sqrt(Variance(values));
|
||||
public static float Variance(this IEnumerable<float> values)
|
||||
{
|
||||
if (values.Count() == 1)
|
||||
return 0;
|
||||
|
||||
float mean = values.Average();
|
||||
float variance = 0.0f;
|
||||
foreach (int value in values)
|
||||
variance += MathF.Pow(value - mean, 2.0f);
|
||||
return variance / values.Count();
|
||||
}
|
||||
public static float StdDev(this IEnumerable<float> values) => MathF.Sqrt(Variance(values));
|
||||
|
||||
public static IEnumerable<IEnumerable<TValue>> Chunk<TValue>(this IEnumerable<TValue> values, IEnumerable<int> chunks)
|
||||
{
|
||||
if (chunks.Count() == 0)
|
||||
throw new InvalidDataException();
|
||||
if (values.Count() != chunks.Sum())
|
||||
throw new InvalidDataException();
|
||||
|
||||
var start = 0;
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
yield return values.Skip(start).Take(chunk);
|
||||
start += chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ public class CameraService : IDisposable
|
||||
CameraManagerStarted = 1 << 1,
|
||||
CameraAcquired = 1 << 2,
|
||||
CameraStarted = 1 << 3,
|
||||
ViewFinderFrameBufferAllocated = 1 << 4,
|
||||
ImageCaptureFrameBufferAllocated = 1 << 5,
|
||||
ImageCaptureFrameBuffersMapped = 1 << 6
|
||||
}
|
||||
@@ -51,10 +50,6 @@ public class CameraService : IDisposable
|
||||
public static CameraServiceConfiguration Default => new CameraServiceConfiguration
|
||||
{
|
||||
CameraId = "",
|
||||
ViewFinderFrameBufferCount = 5,
|
||||
ViewFinderWidth = 4056,
|
||||
ViewFinderHeight = 3080,
|
||||
ViewFinderFps = 30,
|
||||
ImageCaptureFrameBufferCount = 2,
|
||||
ImageCaptureWidth = 4056,
|
||||
ImageCaptureHeight = 3080,
|
||||
@@ -66,10 +61,6 @@ public class CameraService : IDisposable
|
||||
};
|
||||
|
||||
public string CameraId { get; init; } = "";
|
||||
public int ViewFinderFrameBufferCount { get; init; }
|
||||
public int ViewFinderWidth { get; init; }
|
||||
public int ViewFinderHeight { get; init; }
|
||||
public int ViewFinderFps { get; init; }
|
||||
public int ImageCaptureFrameBufferCount { get; init; }
|
||||
public int ImageCaptureWidth { get; init; }
|
||||
public int ImageCaptureHeight { get; init; }
|
||||
@@ -87,16 +78,11 @@ public class CameraService : IDisposable
|
||||
private CameraManager? _cameraManager;
|
||||
private Camera? _camera;
|
||||
private CameraConfiguration? _cameraConfiguration;
|
||||
private StreamConfiguration? _viewFinderStreamConfiguration;
|
||||
private StreamConfiguration? _imageCaptureStreamConfiguration;
|
||||
private FrameBufferAllocator? _viewFinderFrameBufferAllocator;
|
||||
private FrameBufferAllocator? _imageCaptureFrameBufferAllocator;
|
||||
private Stream? _viewFinderStream;
|
||||
private Stream? _imageCaptureStream;
|
||||
private List<MappedBuffer>? _imageCaptureMappedBuffers;
|
||||
private System.Timers.Timer? _viewFinderTimer = null;
|
||||
private System.Timers.ElapsedEventHandler? _viewFinderTimerElapsed = null;
|
||||
private EventHandler<Camera.RequestCompletedEventArgs>? _viewFinderRequestCompletedHandler = null;
|
||||
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
|
||||
private readonly Dictionary<string, object> _cameraHardSettings = new Dictionary<string, object>();
|
||||
|
||||
@@ -176,10 +162,8 @@ public class CameraService : IDisposable
|
||||
CameraSelect(effectiveCancellationToken);
|
||||
CameraAcquire(effectiveCancellationToken);
|
||||
CameraConfigure(effectiveCancellationToken);
|
||||
ViewFinderFrameBuffersAllocate(effectiveCancellationToken);
|
||||
ImageCaptureFrameBuffersAllocate(effectiveCancellationToken);
|
||||
CameraStart(effectiveCancellationToken);
|
||||
ViewFinderFrameBuffersInitialize(effectiveCancellationToken);
|
||||
ImageCaptureFrameBuffersInitialize(effectiveCancellationToken);
|
||||
ImageCaptureFrameBuffersMap(effectiveCancellationToken);
|
||||
}
|
||||
@@ -251,16 +235,8 @@ public class CameraService : IDisposable
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_cameraConfiguration = _camera.GenerateConfiguration(
|
||||
StreamRoleEnum.Viewfinder,
|
||||
StreamRoleEnum.Raw);
|
||||
_cameraConfiguration = _camera.GenerateConfiguration(StreamRoleEnum.Raw);
|
||||
|
||||
_viewFinderStreamConfiguration = _cameraConfiguration.First();
|
||||
_viewFinderStreamConfiguration.BufferCount = checked((uint)_configuration.ViewFinderFrameBufferCount);
|
||||
_viewFinderStreamConfiguration.Size = new Size(
|
||||
checked((uint)_configuration.ViewFinderWidth),
|
||||
checked((uint)_configuration.ViewFinderHeight)
|
||||
);
|
||||
|
||||
_imageCaptureStreamConfiguration = _cameraConfiguration.Skip(1).First();
|
||||
_imageCaptureStreamConfiguration.BufferCount = checked((uint)_configuration.ImageCaptureFrameBufferCount);
|
||||
@@ -275,13 +251,6 @@ public class CameraService : IDisposable
|
||||
throw new Exception("Error while camera configuration validation");
|
||||
}
|
||||
|
||||
_logger.LogDebug(@$"ViewFinder stream configuration:
|
||||
Size: {_viewFinderStreamConfiguration.Size.ToString(true)}
|
||||
FrameSize: {_viewFinderStreamConfiguration.FrameSize}
|
||||
Stride: {_viewFinderStreamConfiguration.Stride}
|
||||
ColorSpace: {_viewFinderStreamConfiguration.ColorSpace?.ToString(true)}
|
||||
PixelFormat: {_viewFinderStreamConfiguration.PixelFormat?.ToString(true)}");
|
||||
|
||||
_logger.LogDebug(@$"ImageCapture stream configuration:
|
||||
Size: {_imageCaptureStreamConfiguration.Size.ToString(true)}
|
||||
FrameSize: {_imageCaptureStreamConfiguration.FrameSize}
|
||||
@@ -298,7 +267,6 @@ public class CameraService : IDisposable
|
||||
throw new Exception($"Error while camera configuration {result}");
|
||||
}
|
||||
|
||||
_viewFinderStream = _viewFinderStreamConfiguration.Stream();
|
||||
_imageCaptureStream = _imageCaptureStreamConfiguration.Stream();
|
||||
}
|
||||
private void CameraStart(CancellationToken cancellationToken)
|
||||
@@ -320,28 +288,6 @@ public class CameraService : IDisposable
|
||||
|
||||
_state |= State.CameraStarted;
|
||||
}
|
||||
private void ViewFinderFrameBuffersAllocate(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_camera is null)
|
||||
throw new InvalidOperationException("Camera not initialized");
|
||||
if (_viewFinderStream is null)
|
||||
throw new InvalidOperationException("ViewFinderStream not initialized");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_viewFinderFrameBufferAllocator = new FrameBufferAllocator(_camera);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
int result = _viewFinderFrameBufferAllocator.Allocate(_viewFinderStream);
|
||||
if (result < 0)
|
||||
{
|
||||
_logger.LogCritical("Error while view finder frame buffers allocation {Result}", result);
|
||||
throw new Exception($"Error while view finder frame buffers allocation {result}");
|
||||
}
|
||||
|
||||
_state |= State.ViewFinderFrameBufferAllocated;
|
||||
}
|
||||
private void ImageCaptureFrameBuffersAllocate(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_camera is null)
|
||||
@@ -364,55 +310,6 @@ public class CameraService : IDisposable
|
||||
|
||||
_state |= State.ImageCaptureFrameBufferAllocated;
|
||||
}
|
||||
private void ViewFinderFrameBuffersInitialize(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_camera is null)
|
||||
throw new InvalidOperationException("Camera not initialized");
|
||||
if (_viewFinderStream is null)
|
||||
throw new InvalidOperationException("ViewFinderStream not initialized");
|
||||
if (_viewFinderFrameBufferAllocator is null)
|
||||
throw new InvalidOperationException("ViewFinderFrameBufferAllocator not initialized");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var buffers = _viewFinderFrameBufferAllocator.Buffers(_viewFinderStream);
|
||||
|
||||
_logger.LogDebug("View finder frame buffers initialization");
|
||||
foreach (var buffer in buffers)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var request = _camera.CreateRequest(0);
|
||||
var result = request.AddBuffer(_viewFinderStream, buffer);
|
||||
if (result < 0)
|
||||
throw new Exception("Error while request create");
|
||||
|
||||
var cts = new TaskCompletionSource();
|
||||
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
|
||||
{
|
||||
if (e.Request.Handle == request.Handle)
|
||||
cts.SetResult();
|
||||
}
|
||||
|
||||
_camera.RequestCompleted += RequestCompleted;
|
||||
result = _camera.QueueRequest(request);
|
||||
if (result < 0)
|
||||
{
|
||||
_camera.RequestCompleted -= RequestCompleted;
|
||||
throw new Exception("Error while request queue");
|
||||
}
|
||||
cts.Task.WaitAsync(cancellationToken).GetAwaiter().GetResult();
|
||||
_camera.RequestCompleted -= RequestCompleted;
|
||||
|
||||
_logger.LogDebug("Request completed: \n\tResult: {} \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers);
|
||||
_logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp);
|
||||
_logger.LogDebug("Request buffer planes: ");
|
||||
foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate())
|
||||
_logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
|
||||
foreach (var (i, plane) in buffer.Planes.Enumerate())
|
||||
_logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset);
|
||||
}
|
||||
}
|
||||
private void ImageCaptureFrameBuffersInitialize(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_camera is null)
|
||||
@@ -826,135 +723,6 @@ public class CameraService : IDisposable
|
||||
return image;
|
||||
}
|
||||
|
||||
public void StartViewFinder()
|
||||
{
|
||||
if (_camera is null)
|
||||
throw new InvalidOperationException("Camera not initialized");
|
||||
if (_viewFinderStream is null)
|
||||
throw new InvalidOperationException("ViewFinderStream not initialized");
|
||||
if (_viewFinderStreamConfiguration is null)
|
||||
throw new InvalidOperationException("ViewFinderStreamConfiguration not initialized");
|
||||
if (_viewFinderFrameBufferAllocator is null)
|
||||
throw new InvalidOperationException("ViewFinderFrameBufferAllocator not initialized");
|
||||
if (_viewFinderTimer is not null ||
|
||||
_viewFinderTimerElapsed is not null ||
|
||||
_viewFinderRequestCompletedHandler is not null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
ViewFinderStarted = true;
|
||||
|
||||
_viewFinderTimer = new System.Timers.Timer()
|
||||
{
|
||||
Interval = 1000 / _configuration.ViewFinderFps,
|
||||
AutoReset = true,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var buffers = _viewFinderFrameBufferAllocator.Buffers(_viewFinderStream);
|
||||
lock (ViewFinderBufferQueue)
|
||||
foreach (var buffer in buffers)
|
||||
ViewFinderBufferQueue.Add(buffer);
|
||||
|
||||
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs ea)
|
||||
{
|
||||
if (ea.Request.Cookie != 2)
|
||||
return;
|
||||
var buffer = ea.Request.FindBuffer(_viewFinderStream);
|
||||
// _logger.LogInformation("Completed request for frame buffer {}", buffer);
|
||||
lock (ViewFinderBufferQueue)
|
||||
ViewFinderBufferQueue.Add(buffer);
|
||||
}
|
||||
_viewFinderRequestCompletedHandler = RequestCompleted;
|
||||
_camera.RequestCompleted += _viewFinderRequestCompletedHandler;
|
||||
|
||||
void TimerElapsed(object? sender, System.Timers.ElapsedEventArgs ea)
|
||||
{
|
||||
if (IsImageCapturing)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
FrameBuffer buffer;
|
||||
lock (ViewFinderBufferQueue)
|
||||
{
|
||||
if (ViewFinderBufferQueue.Count() > 0 && (
|
||||
ViewFinderBufferQueue.First().Request.Cookie != 2 ||
|
||||
ViewFinderBufferQueue.First().Request.Status == Request.StatusEnum.RequestComplete ||
|
||||
ViewFinderBufferQueue.First().Request.Status == Request.StatusEnum.RequestCancelled))
|
||||
{
|
||||
buffer = ViewFinderBufferQueue.First();
|
||||
ViewFinderBufferQueue.Remove(buffer);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
var request = _camera.CreateRequest(2);
|
||||
request.AddBuffer(_viewFinderStream, buffer);
|
||||
_camera.QueueRequest(request);
|
||||
// _logger.LogInformation("Queued request for frame buffer {}", buffer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while ViewFinder tick");
|
||||
}
|
||||
}
|
||||
_viewFinderTimerElapsed = TimerElapsed;
|
||||
_viewFinderTimer.Elapsed += _viewFinderTimerElapsed;
|
||||
_viewFinderTimer.Start();
|
||||
|
||||
_logger.LogInformation("ViewFinder started");
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while ViewFinder start");
|
||||
_viewFinderTimer.Stop();
|
||||
_viewFinderTimer.Dispose();
|
||||
_viewFinderTimer = null;
|
||||
_viewFinderTimerElapsed = null;
|
||||
_camera.RequestCompleted -= _viewFinderRequestCompletedHandler;
|
||||
_viewFinderRequestCompletedHandler = null;
|
||||
lock (ViewFinderBufferQueue)
|
||||
ViewFinderBufferQueue.Clear();
|
||||
}
|
||||
}
|
||||
public void StopViewFinder()
|
||||
{
|
||||
ViewFinderStarted = false;
|
||||
_viewFinderTimer?.Stop();
|
||||
_viewFinderTimer?.Dispose();
|
||||
_viewFinderTimer = null;
|
||||
_viewFinderTimerElapsed = null;
|
||||
_camera?.RequestCompleted -= _viewFinderRequestCompletedHandler;
|
||||
_viewFinderRequestCompletedHandler = null;
|
||||
lock (ViewFinderBufferQueue)
|
||||
ViewFinderBufferQueue.Clear();
|
||||
}
|
||||
public (FrameBuffer, StreamConfiguration)? GrabViewFinderFrame()
|
||||
{
|
||||
if (IsImageCapturing)
|
||||
return null;
|
||||
|
||||
FrameBuffer? buffer = null;
|
||||
lock (ViewFinderBufferQueue)
|
||||
if (ViewFinderBufferQueue.Count() > 0)
|
||||
{
|
||||
buffer = ViewFinderBufferQueue.LastOrDefault();
|
||||
if (buffer is not null)
|
||||
ViewFinderBufferQueue.Remove(buffer);
|
||||
}
|
||||
if (buffer is not null && _viewFinderStreamConfiguration is not null)
|
||||
return (buffer, _viewFinderStreamConfiguration);
|
||||
return null;
|
||||
}
|
||||
public void ReturnViewFinderFrame(FrameBuffer frameBuffer)
|
||||
{
|
||||
lock (ViewFinderBufferQueue)
|
||||
ViewFinderBufferQueue.Insert(0, frameBuffer);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
@@ -986,8 +754,6 @@ public class CameraService : IDisposable
|
||||
}
|
||||
_imageCaptureMappedBuffers.Clear();
|
||||
}
|
||||
if (_state.HasFlag(State.ViewFinderFrameBufferAllocated) && _viewFinderStream is not null)
|
||||
_viewFinderFrameBufferAllocator?.Free(_viewFinderStream);
|
||||
if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null)
|
||||
_imageCaptureFrameBufferAllocator?.Free(_imageCaptureStream);
|
||||
if (_state.HasFlag(State.CameraStarted))
|
||||
@@ -997,23 +763,17 @@ public class CameraService : IDisposable
|
||||
if (_state.HasFlag(State.CameraManagerStarted))
|
||||
_cameraManager?.Stop();
|
||||
|
||||
// _viewFinderStream?.Dispose();
|
||||
// _imageCaptureStream?.Dispose();
|
||||
// _viewFinderFrameBufferAllocator?.Dispose();
|
||||
// _imageCaptureFrameBufferAllocator?.Dispose();
|
||||
_cameraManager?.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(e, "Error while CameraService cleanup");
|
||||
_logger.LogError(ex, "Error while CameraService cleanup");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cameraManager = null;
|
||||
_camera = null;
|
||||
_viewFinderFrameBufferAllocator = null;
|
||||
_imageCaptureFrameBufferAllocator = null;
|
||||
_viewFinderStream = null;
|
||||
_imageCaptureStream = null;
|
||||
_imageCaptureMappedBuffers = null;
|
||||
_state = State.NotInitialized;
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
using System.Globalization;
|
||||
|
||||
using GSS2.Core.Extensions;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GSS2.Core.Hardware;
|
||||
|
||||
public class ComputeResourcesService
|
||||
{
|
||||
public record CpuUsage(double AllUsagePercent, List<double> CoreUsagePercents);
|
||||
public record CpuTemperature(double Current, double Critical);
|
||||
public record MemoryUsage(
|
||||
long TotalB, long UsedB, long AvailableB,
|
||||
double TotalkB, double UsedkB, double AvailablekB,
|
||||
double TotalMB, double UsedMB, double AvailableMB,
|
||||
double TotalGB, double UsedGB, double AvailableGB)
|
||||
{
|
||||
public MemoryUsage(long totalB, long usedB, long availableB) :
|
||||
this(
|
||||
TotalB: totalB, UsedB: usedB, AvailableB: availableB,
|
||||
TotalkB: totalB / 1000, UsedkB: usedB / 1000, AvailablekB: availableB / 1000,
|
||||
TotalMB: totalB / Math.Pow(1000.0, 2), UsedMB: usedB / Math.Pow(1000.0, 2), AvailableMB: availableB / Math.Pow(1000.0, 2),
|
||||
TotalGB: totalB / Math.Pow(1000.0, 3), UsedGB: usedB / Math.Pow(1000.0, 3), AvailableGB: availableB / Math.Pow(1000.0, 3)
|
||||
)
|
||||
{ }
|
||||
}
|
||||
public record ComputeResourcesSnapshot(CpuUsage CpuUsage, MemoryUsage MemoryUsage, CpuTemperature CpuTemperature, DateTime Timestamp);
|
||||
private readonly ILogger<ComputeResourcesService> _logger;
|
||||
|
||||
private (long idle, long total)? _prevAll = null;
|
||||
private Dictionary<int, (long idle, long total)?>? _prevCores = null;
|
||||
|
||||
public ComputeResourcesService(ILogger<ComputeResourcesService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Initialization");
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
public ComputeResourcesSnapshot GetSnapshot()
|
||||
{
|
||||
return new ComputeResourcesSnapshot(
|
||||
CpuUsage: GetCpuUsage(),
|
||||
CpuTemperature: GetCpuTemperature(),
|
||||
MemoryUsage: GetMemoryUsage(),
|
||||
Timestamp: DateTime.UtcNow
|
||||
);
|
||||
}
|
||||
private CpuUsage GetCpuUsage()
|
||||
{
|
||||
static (double usage, long idle, long total) ParseLines(string line, (long idle, long total)? prev)
|
||||
{
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
long user = long.Parse(parts[1]);
|
||||
long nice = long.Parse(parts[2]);
|
||||
long system = long.Parse(parts[3]);
|
||||
long idle = long.Parse(parts[4]);
|
||||
long iowait = line.Length > 5 ? long.Parse(parts[5]) : 0;
|
||||
long irq = line.Length > 6 ? long.Parse(parts[6]) : 0;
|
||||
long softirq = line.Length > 7 ? long.Parse(parts[7]) : 0;
|
||||
|
||||
long idleTime = idle + iowait;
|
||||
long totalTime = idleTime + user + nice + system + irq + softirq;
|
||||
|
||||
double usage = 0;
|
||||
|
||||
if (prev is not null)
|
||||
{
|
||||
long totalDiff = totalTime - prev.Value.total;
|
||||
long idleDiff = idleTime - prev.Value.idle;
|
||||
|
||||
|
||||
if (totalDiff > 0)
|
||||
usage = (double)(totalDiff - idleDiff) / totalDiff * 100.0;
|
||||
}
|
||||
|
||||
return (usage, idleTime, totalTime);
|
||||
}
|
||||
|
||||
var lines = File.ReadLines("/proc/stat").Where(l => l.StartsWith("cpu"));
|
||||
if (lines.Count() == 0)
|
||||
return new CpuUsage(0, new List<double>());
|
||||
|
||||
var (allUsage, allIdle, allTotal) = ParseLines(lines.First(), _prevAll);
|
||||
_prevAll = (allIdle, allTotal);
|
||||
|
||||
var coreUsages = new List<double>();
|
||||
|
||||
foreach (var (i, line) in lines.Skip(1).Enumerate())
|
||||
{
|
||||
if (_prevCores is null)
|
||||
_prevCores = new Dictionary<int, (long idle, long total)?>();
|
||||
if (!_prevCores.ContainsKey(i))
|
||||
_prevCores.Add(i, null);
|
||||
var (coreUsage, coreIdle, coreTotal) = ParseLines(line, _prevCores[i]);
|
||||
_prevCores[i] = (coreIdle, coreTotal);
|
||||
coreUsages.Add(coreUsage);
|
||||
}
|
||||
|
||||
return new CpuUsage(
|
||||
AllUsagePercent: allUsage,
|
||||
CoreUsagePercents: coreUsages
|
||||
);
|
||||
}
|
||||
private CpuTemperature GetCpuTemperature()
|
||||
{
|
||||
var currentStr = File.ReadAllText("/sys/class/thermal/thermal_zone0/temp").Trim();
|
||||
var current = int.Parse(currentStr) / 1000.0;
|
||||
|
||||
var criticalFile = Directory.GetFiles("/sys/class/thermal/thermal_zone0/", "trip_point_*_type")
|
||||
.FirstOrDefault(f => File.ReadAllText(f).Trim() == "critical")?
|
||||
.Replace("_type", "_temp");
|
||||
|
||||
double critical = 100.0;
|
||||
if (criticalFile is not null)
|
||||
{
|
||||
var criticalStr = File.ReadAllText(criticalFile).Trim();
|
||||
critical = int.Parse(criticalStr) / 1000.0;
|
||||
}
|
||||
|
||||
return new CpuTemperature(
|
||||
Current: current,
|
||||
Critical: critical
|
||||
);
|
||||
}
|
||||
private MemoryUsage GetMemoryUsage()
|
||||
{
|
||||
var memInfo = File.ReadAllLines("/proc/meminfo")
|
||||
.Select(l => l.Split(':', 2))
|
||||
.ToDictionary(
|
||||
p => p[0],
|
||||
p => long.Parse(p[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture) * 1024
|
||||
);
|
||||
|
||||
long total = memInfo["MemTotal"];
|
||||
long available = memInfo.ContainsKey("MemAvailable") ? memInfo["MemAvailable"] : memInfo["MemFree"];
|
||||
|
||||
long used = total - available;
|
||||
|
||||
return new MemoryUsage(totalB: total, usedB: used, availableB: available);
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,18 @@ public class LightsService
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = LightsServiceConfiguration.Default;
|
||||
|
||||
_logger.LogWarning("Configuration not set. Using default configuration.");
|
||||
}
|
||||
_configuration = configuration;
|
||||
_logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4));
|
||||
|
||||
_client = new Lights.LightsClient(GrpcChannel.ForAddress(_configuration.ServerAddress));
|
||||
var channelOptions = new GrpcChannelOptions
|
||||
{
|
||||
Credentials = ChannelCredentials.Insecure,
|
||||
HttpHandler = new SocketsHttpHandler(),
|
||||
MaxReconnectBackoff = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
_client = new Lights.LightsClient(GrpcChannel.ForAddress(_configuration.ServerAddress, channelOptions));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
using System.Device.I2c;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using GSS2.Core.Abstractions;
|
||||
|
||||
namespace GSS2.Core.Hardware;
|
||||
|
||||
public class TemperatureHumidityService : IDisposable
|
||||
{
|
||||
public record TemperatureHumidityServiceConfiguration : ServiceConfigurationBase
|
||||
{
|
||||
public static TemperatureHumidityServiceConfiguration Default => new TemperatureHumidityServiceConfiguration
|
||||
{
|
||||
Bus = 6,
|
||||
Address = 0x45
|
||||
};
|
||||
|
||||
public int Bus { get; init; }
|
||||
public int Address { get; init; }
|
||||
}
|
||||
private bool _disposedValue;
|
||||
private readonly ILogger<TemperatureHumidityService> _logger;
|
||||
private readonly TemperatureHumidityServiceConfiguration _configuration;
|
||||
|
||||
private readonly I2cDevice _sensor;
|
||||
|
||||
public TemperatureHumidityService(ILogger<TemperatureHumidityService> logger, TemperatureHumidityServiceConfiguration? configuration = null)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
_logger.LogInformation("Initialization");
|
||||
|
||||
try
|
||||
{
|
||||
if (configuration is null)
|
||||
{
|
||||
configuration = TemperatureHumidityServiceConfiguration.Default;
|
||||
_logger.LogWarning("Configuration not set. Using default configuration.");
|
||||
}
|
||||
_configuration = configuration;
|
||||
_logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4));
|
||||
|
||||
_sensor = I2cDevice.Create(new I2cConnectionSettings(_configuration.Bus, _configuration.Address));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogCritical(e, "An exception occurred while initialization");
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Initialized");
|
||||
}
|
||||
|
||||
public (double temperature, double relativeHumidity) Measure(int measurementsCount = 1, bool checkCrc = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// В соответствии с даташитом на SHT45-AD1B-R2 псевдокод измерения с высокой точностью следующий:
|
||||
// i2c_write(i2c_addr=0x44, tx_bytes=[0xFD])
|
||||
// wait_seconds(0.01)
|
||||
// rx_bytes = i2c_read(i2c_addr=0x44, number_of_bytes=6)
|
||||
// t_ticks = rx_bytes[0] * 256 + rx_bytes[1]
|
||||
// checksum_t = rx_bytes[2]
|
||||
// rh_ticks = rx_bytes[3] * 256 + rx_bytes[4]
|
||||
// checksum_rh = rx_bytes[5]
|
||||
// t_degC = -45 + 175 * t_ticks/65535
|
||||
// rh_pRH = -6 + 125 * rh_ticks/65535
|
||||
// if (rh_pRH > 100):
|
||||
// rh_pRH = 100
|
||||
// if (rh_pRH < 0):
|
||||
// rh_pRH = 0
|
||||
|
||||
if (measurementsCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(measurementsCount));
|
||||
|
||||
double sumTemperature = 0;
|
||||
double sumRelativeHumidity = 0;
|
||||
int successMeasurementsCount = 0;
|
||||
|
||||
Span<byte> command = stackalloc byte[] { 0xFD };
|
||||
Span<byte> data = stackalloc byte[6];
|
||||
|
||||
for (int i = 0; i < measurementsCount; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
// Отправляем команду
|
||||
_sensor.Write(command);
|
||||
// 10 мс ожидания
|
||||
Task.Delay(10, cancellationToken);
|
||||
// Читаем данные
|
||||
_sensor.Read(data);
|
||||
|
||||
// Распаковываем данные [2 * 8-bit T-data; 8-bit CRC; 2 * 8-bit RH-data; 8-bit CRC]
|
||||
byte temperatureChecksum = data[2];
|
||||
byte relativeHumidityChecksum = data[5];
|
||||
ushort temperatureTicks = BitConverter.ToUInt16(data.Slice(0, 2));
|
||||
int relativeHumidityTicks = BitConverter.ToUInt16(data.Slice(3, 2));
|
||||
|
||||
// Проверка по CRC
|
||||
if (checkCrc && (!CheckCrc(data.Slice(0, 2), temperatureChecksum) ||
|
||||
!CheckCrc(data.Slice(3, 2), relativeHumidityChecksum)))
|
||||
{
|
||||
_logger.LogWarning("Temperature and humidity sensor CRC error");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Пересчёт
|
||||
double temperature = -45 + 175 * (temperatureTicks / 65535.0);
|
||||
double relativeHumidity = -6 + 125 * (relativeHumidityTicks / 65535.0);
|
||||
relativeHumidity = Math.Clamp(relativeHumidity, 0, 100);
|
||||
|
||||
// Сумма для усреднения
|
||||
sumTemperature += temperature;
|
||||
sumRelativeHumidity += relativeHumidity;
|
||||
successMeasurementsCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Temperature and humidity sensor not available on I2C bus");
|
||||
}
|
||||
}
|
||||
|
||||
if (successMeasurementsCount == 0)
|
||||
{
|
||||
_logger.LogError("Temperature and humidity sensor: no successful measurements");
|
||||
return (double.NaN, double.NaN);
|
||||
}
|
||||
|
||||
return (sumTemperature / successMeasurementsCount, sumRelativeHumidity / successMeasurementsCount);
|
||||
}
|
||||
|
||||
private static bool CheckCrc(Span<byte> data, byte expectedCrc)
|
||||
{
|
||||
byte crc = 0xFF;
|
||||
foreach (var b in data)
|
||||
{
|
||||
crc ^= b;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if ((crc & 0x80) != 0)
|
||||
crc = (byte)((crc << 1) ^ 0x31);
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc == expectedCrc;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
_sensor?.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace GSS2.Core;
|
||||
|
||||
public class ImageStorageService
|
||||
{
|
||||
private readonly ILogger<ImageStorageService> _logger;
|
||||
private readonly DirectoryInfo _rootDirectory;
|
||||
|
||||
public ImageStorageService(ILogger<ImageStorageService> logger, DirectoryInfo rootDirectory)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger.LogInformation("Инициализация");
|
||||
|
||||
_rootDirectory = rootDirectory;
|
||||
if (!_rootDirectory.Exists)
|
||||
{
|
||||
_rootDirectory.Create();
|
||||
_logger.LogInformation("Создана директория: {}", _rootDirectory.FullName);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Инициализировано");
|
||||
}
|
||||
|
||||
public async Task<string> SaveAsync(Stream fileStream, string extension, string subDirectory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var fileName = GenerateFileName(extension);
|
||||
var fullPath = GetFullPath(fileName, subDirectory);
|
||||
System.Diagnostics.Debug.Assert(fullPath is not null);
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath)!;
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
await using var file = new FileStream(
|
||||
fullPath,
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
81920,
|
||||
useAsync: true);
|
||||
|
||||
await fileStream.CopyToAsync(file, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Файл сохранён: {}", fileName);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public Stream OpenRead(string fileName, string? subDirectory = null)
|
||||
{
|
||||
var fullPath = GetFullPath(fileName, subDirectory);
|
||||
if (fullPath is null)
|
||||
throw new FileNotFoundException(null, fullPath);
|
||||
return new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
public bool Delete(string fileName, string? subDirectory = null)
|
||||
{
|
||||
var fullPath = GetFullPath(fileName, subDirectory);
|
||||
if (fullPath is null)
|
||||
throw new FileNotFoundException(null, fullPath);
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
return false;
|
||||
|
||||
File.Delete(fullPath);
|
||||
_logger.LogInformation("Файл удалён: {}", fileName);
|
||||
|
||||
var parentDirectory = Directory.GetParent(fullPath);
|
||||
if (parentDirectory?.GetFiles().Count() == 0)
|
||||
{
|
||||
parentDirectory.Delete();
|
||||
_logger.LogInformation("Удалена пустая директория: {}", parentDirectory.Name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GenerateFileName(string extension)
|
||||
{
|
||||
var guid = Guid.NewGuid().ToString("N");
|
||||
return $"{guid}{extension}";
|
||||
}
|
||||
|
||||
public string? GetFullPath(string fileName, string? subDirectory = null)
|
||||
{
|
||||
if (fileName.Count() < 4)
|
||||
return null;
|
||||
|
||||
var level1 = fileName.Substring(0, 2);
|
||||
var level2 = fileName.Substring(2, 2);
|
||||
|
||||
if (subDirectory is not null)
|
||||
return Path.Combine(_rootDirectory.FullName, subDirectory, level1, level2, fileName);
|
||||
|
||||
return ListFiles()
|
||||
.FirstOrDefault(f => Path.GetFileName(f) == fileName);
|
||||
}
|
||||
|
||||
public List<string> ListFiles(string? subDirectory = null)
|
||||
{
|
||||
if (subDirectory is not null)
|
||||
return Directory.GetDirectories(Path.Join(_rootDirectory.FullName, subDirectory))
|
||||
.SelectMany(Directory.GetDirectories)
|
||||
.SelectMany(Directory.GetFiles)
|
||||
.ToList();
|
||||
|
||||
return Directory.GetDirectories(_rootDirectory.FullName)
|
||||
.Select(Path.GetFileName)
|
||||
.SelectMany(ListFiles)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,8 @@ public sealed class FileLoggerProvider : ILoggerProvider
|
||||
|
||||
public ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, _filePath);
|
||||
|
||||
public void Dispose() { }
|
||||
public void Dispose()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.IO.Compression;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Компрессор для больших списков чисел с плавающей точкой
|
||||
/// </summary>
|
||||
public class CompressedFloatListConverter : ValueConverter<List<float>, string>
|
||||
{
|
||||
public CompressedFloatListConverter()
|
||||
: base(
|
||||
v => Compress(v),
|
||||
v => Decompress(v)
|
||||
)
|
||||
{ }
|
||||
|
||||
private static string Compress(List<float> list)
|
||||
{
|
||||
if (list is null || list.Count == 0)
|
||||
return "";
|
||||
|
||||
byte[] floatBytes = new byte[list.Count * sizeof(float)];
|
||||
Buffer.BlockCopy(list.ToArray(), 0, floatBytes, 0, floatBytes.Length);
|
||||
|
||||
using var outputStream = new MemoryStream();
|
||||
using (var compressionStream = new BrotliStream(outputStream, CompressionLevel.Optimal))
|
||||
compressionStream.Write(floatBytes, 0, floatBytes.Length);
|
||||
|
||||
return Convert.ToBase64String(outputStream.ToArray());
|
||||
}
|
||||
|
||||
private static List<float> Decompress(string base64String)
|
||||
{
|
||||
if (string.IsNullOrEmpty(base64String))
|
||||
return new List<float>();
|
||||
|
||||
byte[] compressedBytes;
|
||||
float[]? floatArray;
|
||||
|
||||
try
|
||||
{
|
||||
compressedBytes = Convert.FromBase64String(base64String);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
floatArray = JsonConvert.DeserializeObject<float[]>(base64String);
|
||||
if (floatArray is null)
|
||||
throw;
|
||||
return new List<float>(floatArray);
|
||||
}
|
||||
|
||||
using var inputStream = new MemoryStream(compressedBytes);
|
||||
using var decompressionStream = new BrotliStream(inputStream, CompressionMode.Decompress);
|
||||
using var outputStream = new MemoryStream();
|
||||
decompressionStream.CopyTo(outputStream);
|
||||
|
||||
byte[] decompressedBytes = outputStream.ToArray();
|
||||
floatArray = new float[decompressedBytes.Length / sizeof(float)];
|
||||
Buffer.BlockCopy(decompressedBytes, 0, floatArray, 0, decompressedBytes.Length);
|
||||
|
||||
return new List<float>(floatArray);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user