Files
XplorePlane/XP.ReportEngine/Services/ProcessorDataAdapter.cs
T

534 lines
22 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using XP.Common.Logging.Interfaces;
using XP.ReportEngine.Interfaces;
using XP.ReportEngine.Models;
namespace XP.ReportEngine.Services
{
/// <summary>
/// 处理器数据适配器实现 | Processor data adapter implementation
/// 将 XP.ImageProcessing 的 ProcessorOutput 转换为 ReportContext
/// Converts XP.ImageProcessing ProcessorOutput to ReportContext
/// </summary>
public class ProcessorDataAdapter : IReportDataAdapter
{
private readonly ILoggerService _logger;
// 处理器类型常量 | Processor type constants
private const string LineMeasurementProcessor = "LineMeasurementProcessor";
private const string BgaVoidRateProcessor = "BgaVoidRateProcessor";
private const string VoidMeasurementProcessor = "VoidMeasurementProcessor";
private const string FillRateProcessor = "FillRateProcessor";
public ProcessorDataAdapter(ILoggerService logger)
{
_logger = logger?.ForModule<ProcessorDataAdapter>() ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context
/// </summary>
public ReportContext Adapt(List<ProcessorOutput> processorOutputs, ReportMetadata metadata)
{
if (processorOutputs == null) throw new ArgumentNullException(nameof(processorOutputs));
if (metadata == null) throw new ArgumentNullException(nameof(metadata));
_logger.Info("开始数据适配,处理器数量: {Count} | Starting data adaptation, processor count: {Count}", processorOutputs.Count);
var context = new ReportContext
{
Metadata = metadata,
ResultGroups = new List<InspectionResultGroup>(),
Images = new Dictionary<string, ImageData>(),
Properties = new Dictionary<string, object>()
};
// 多处理器输出聚合逻辑:每个 ProcessorOutput 生成一个 InspectionResultGroup | Aggregation: each ProcessorOutput becomes one InspectionResultGroup
for (var i = 0; i < processorOutputs.Count; i++)
{
var output = processorOutputs[i];
if (output == null)
{
_logger.Warn("处理器输出为 null,索引: {Index},已跳过 | Processor output is null at index: {Index}, skipped", i);
continue;
}
var group = AdaptProcessorOutput(output, i);
context.ResultGroups.Add(group);
// 将结果数据扁平化到 Properties(供模板 ${key} 表达式绑定)
// Flatten result data to Properties (for template ${key} expression binding)
if (group.Data != null)
{
foreach (var kvp in group.Data)
{
context.Properties[kvp.Key] = kvp.Value;
}
}
// 将 Classification 也放入 Properties | Also put Classification into Properties
if (!string.IsNullOrEmpty(group.Classification))
{
context.Properties["classification"] = group.Classification;
}
// 将表格数据以模板期望的 dataKey 存入 Properties
// Store table data with template-expected dataKey into Properties
if (group.TableRows != null && group.TableRows.Count > 0)
{
var tableKey = GetTableDataKey(output.ProcessorType);
if (!string.IsNullOrEmpty(tableKey))
{
context.Properties[tableKey] = group.TableRows;
}
}
// 关联标注图像(使用模板期望的 dataKey)
// Associate annotated image (using template-expected dataKey)
if (output.AnnotatedImage != null)
{
var imageKey = GetImageDataKey(output.ProcessorType);
context.Images[imageKey] = output.AnnotatedImage;
// 同时保留原始键名以兼容其他调用方 | Also keep original key for other callers
var originalKey = $"{output.ProcessorType}_{i}_annotated";
context.Images[originalKey] = output.AnnotatedImage;
}
}
_logger.Info("数据适配完成,生成 {Count} 个结果分组 | Data adaptation completed, generated {Count} result groups", context.ResultGroups.Count);
return context;
}
/// <summary>
/// 根据处理器类型分发适配逻辑 | Dispatch adaptation logic by processor type
/// </summary>
private InspectionResultGroup AdaptProcessorOutput(ProcessorOutput output, int index)
{
var sourceId = $"{output.ProcessorType}_{index}";
return output.ProcessorType switch
{
LineMeasurementProcessor => AdaptLineMeasurement(output, sourceId),
BgaVoidRateProcessor => AdaptBgaVoidRate(output, sourceId),
VoidMeasurementProcessor => AdaptVoidMeasurement(output, sourceId),
FillRateProcessor => AdaptFillRate(output, sourceId),
_ => AdaptGeneric(output, sourceId)
};
}
#region LineMeasurementProcessor | LineMeasurementProcessor Adaptation
/// <summary>
/// 适配线测量处理器输出 | Adapt line measurement processor output
/// 提取 MeasurementType、Point1、Point2、PixelDistance、ActualDistance、Unit、Angle
/// </summary>
private InspectionResultGroup AdaptLineMeasurement(ProcessorOutput output, string sourceId)
{
_logger.Debug("适配 LineMeasurementProcessor 输出,SourceId: {SourceId} | Adapting LineMeasurementProcessor output, SourceId: {SourceId}", sourceId);
var data = output.OutputData ?? new Dictionary<string, object>();
var group = new InspectionResultGroup
{
ProcessorType = LineMeasurementProcessor,
SourceId = sourceId,
Classification = string.Empty,
Data = new Dictionary<string, object>
{
["measurementType"] = GetValueOrDefault<string>(data, "MeasurementType", string.Empty),
["point1"] = GetValueOrDefault<object>(data, "Point1", null),
["point2"] = GetValueOrDefault<object>(data, "Point2", null),
["pixelDistance"] = GetValueOrDefault<double>(data, "PixelDistance", 0.0),
["actualDistance"] = GetValueOrDefault<double>(data, "ActualDistance", 0.0),
["unit"] = GetValueOrDefault<string>(data, "Unit", string.Empty),
["angle"] = GetValueOrDefault<double>(data, "Angle", 0.0)
},
TableRows = new List<Dictionary<string, object>>()
};
return group;
}
#endregion
#region BgaVoidRateProcessor | BgaVoidRateProcessor Adaptation
/// <summary>
/// 适配 BGA 气泡率处理器输出 | Adapt BGA void rate processor output
/// 提取 BgaCount、BgaBalls 列表转 TableRows、VoidRate、FillRate、TotalBgaArea、TotalVoidArea、Classification、VoidLimit
/// </summary>
private InspectionResultGroup AdaptBgaVoidRate(ProcessorOutput output, string sourceId)
{
_logger.Debug("适配 BgaVoidRateProcessor 输出,SourceId: {SourceId} | Adapting BgaVoidRateProcessor output, SourceId: {SourceId}", sourceId);
var data = output.OutputData ?? new Dictionary<string, object>();
var group = new InspectionResultGroup
{
ProcessorType = BgaVoidRateProcessor,
SourceId = sourceId,
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
Data = new Dictionary<string, object>
{
["bgaCount"] = GetValueOrDefault<int>(data, "BgaCount", 0),
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
["fillRate"] = GetValueOrDefault<double>(data, "FillRate", 0.0),
["totalBgaArea"] = GetValueOrDefault<double>(data, "TotalBgaArea", 0.0),
["totalVoidArea"] = GetValueOrDefault<double>(data, "TotalVoidArea", 0.0),
["voidLimit"] = GetValueOrDefault<double>(data, "VoidLimit", 0.0)
},
TableRows = ConvertBgaBallsToTableRows(data)
};
return group;
}
/// <summary>
/// 将 BgaBalls 列表转换为表格行 | Convert BgaBalls list to table rows
/// 每个焊球一行,包含 index、voidRate、area、classification
/// </summary>
private List<Dictionary<string, object>> ConvertBgaBallsToTableRows(Dictionary<string, object> data)
{
var tableRows = new List<Dictionary<string, object>>();
var bgaBalls = GetListValue(data, "BgaBalls");
if (bgaBalls == null || bgaBalls.Count == 0)
{
return tableRows;
}
for (var i = 0; i < bgaBalls.Count; i++)
{
var ball = bgaBalls[i];
var row = new Dictionary<string, object>
{
["index"] = i + 1,
["voidRate"] = GetNestedValue<double>(ball, "VoidRate", 0.0),
["area"] = GetNestedValue<double>(ball, "Area", 0.0),
["classification"] = GetNestedValue<string>(ball, "Classification", string.Empty)
};
tableRows.Add(row);
}
return tableRows;
}
#endregion
#region VoidMeasurementProcessor | VoidMeasurementProcessor Adaptation
/// <summary>
/// 适配空隙测量处理器输出 | Adapt void measurement processor output
/// 提取 RoiArea、TotalVoidArea、VoidRate、VoidLimit、VoidCount、MaxVoidArea、Classification、Voids 列表转 TableRows
/// </summary>
private InspectionResultGroup AdaptVoidMeasurement(ProcessorOutput output, string sourceId)
{
_logger.Debug("适配 VoidMeasurementProcessor 输出,SourceId: {SourceId} | Adapting VoidMeasurementProcessor output, SourceId: {SourceId}", sourceId);
var data = output.OutputData ?? new Dictionary<string, object>();
var group = new InspectionResultGroup
{
ProcessorType = VoidMeasurementProcessor,
SourceId = sourceId,
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
Data = new Dictionary<string, object>
{
["roiArea"] = GetValueOrDefault<double>(data, "RoiArea", 0.0),
["totalVoidArea"] = GetValueOrDefault<double>(data, "TotalVoidArea", 0.0),
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
["voidLimit"] = GetValueOrDefault<double>(data, "VoidLimit", 0.0),
["voidCount"] = GetValueOrDefault<int>(data, "VoidCount", 0),
["maxVoidArea"] = GetValueOrDefault<double>(data, "MaxVoidArea", 0.0)
},
TableRows = ConvertVoidsToTableRows(data)
};
return group;
}
/// <summary>
/// 将 Voids 列表转换为表格行 | Convert Voids list to table rows
/// 每个空隙一行,包含 index、area、areaPercent、centerX、centerY
/// </summary>
private List<Dictionary<string, object>> ConvertVoidsToTableRows(Dictionary<string, object> data)
{
var tableRows = new List<Dictionary<string, object>>();
var voids = GetListValue(data, "Voids");
if (voids == null || voids.Count == 0)
{
return tableRows;
}
for (var i = 0; i < voids.Count; i++)
{
var voidItem = voids[i];
var row = new Dictionary<string, object>
{
["index"] = i + 1,
["area"] = GetNestedValue<double>(voidItem, "Area", 0.0),
["areaPercent"] = GetNestedValue<double>(voidItem, "AreaPercent", 0.0),
["centerX"] = GetNestedValue<double>(voidItem, "CenterX", 0.0),
["centerY"] = GetNestedValue<double>(voidItem, "CenterY", 0.0)
};
tableRows.Add(row);
}
return tableRows;
}
#endregion
#region FillRateProcessor | FillRateProcessor Adaptation
/// <summary>
/// 适配填锡率处理器输出 | Adapt fill rate processor output
/// 提取 FillRate、VoidRate、FullDistance、FillDistance、THTLimit、Classification、E1-E4 椭圆几何数据
/// </summary>
private InspectionResultGroup AdaptFillRate(ProcessorOutput output, string sourceId)
{
_logger.Debug("适配 FillRateProcessor 输出,SourceId: {SourceId} | Adapting FillRateProcessor output, SourceId: {SourceId}", sourceId);
var data = output.OutputData ?? new Dictionary<string, object>();
var group = new InspectionResultGroup
{
ProcessorType = FillRateProcessor,
SourceId = sourceId,
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
Data = new Dictionary<string, object>
{
["fillRate"] = GetValueOrDefault<double>(data, "FillRate", 0.0),
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
["fullDistance"] = GetValueOrDefault<double>(data, "FullDistance", 0.0),
["fillDistance"] = GetValueOrDefault<double>(data, "FillDistance", 0.0),
["thtLimit"] = GetValueOrDefault<double>(data, "THTLimit", 0.0),
["e1"] = GetValueOrDefault<object>(data, "E1", null),
["e2"] = GetValueOrDefault<object>(data, "E2", null),
["e3"] = GetValueOrDefault<object>(data, "E3", null),
["e4"] = GetValueOrDefault<object>(data, "E4", null)
},
TableRows = new List<Dictionary<string, object>>()
};
return group;
}
#endregion
#region | Generic Adaptation
/// <summary>
/// 通用处理器适配(未知类型)| Generic processor adaptation (unknown type)
/// 将所有 OutputData 键值对直接映射到 Data 字典
/// </summary>
private InspectionResultGroup AdaptGeneric(ProcessorOutput output, string sourceId)
{
_logger.Warn("未知处理器类型: {ProcessorType},使用通用适配 | Unknown processor type: {ProcessorType}, using generic adaptation", output.ProcessorType);
var data = output.OutputData ?? new Dictionary<string, object>();
var group = new InspectionResultGroup
{
ProcessorType = output.ProcessorType ?? "Unknown",
SourceId = sourceId,
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
Data = new Dictionary<string, object>(),
TableRows = new List<Dictionary<string, object>>()
};
// 将所有键值对转为小驼峰命名映射 | Map all key-value pairs with camelCase naming
foreach (var kvp in data)
{
var camelKey = ToCamelCase(kvp.Key);
group.Data[camelKey] = kvp.Value;
}
return group;
}
#endregion
#region | Helper Methods
/// <summary>
/// 从字典中获取值,缺失时使用默认值并记录警告 | Get value from dictionary, use default and log warning if missing
/// </summary>
private T GetValueOrDefault<T>(Dictionary<string, object> data, string key, T defaultValue)
{
if (data == null || !data.TryGetValue(key, out var value) || value == null)
{
_logger.Warn("处理器输出缺少键: {Key},使用默认值: {Default} | Processor output missing key: {Key}, using default: {Default}", key, defaultValue);
return defaultValue;
}
try
{
return ConvertValue<T>(value);
}
catch (Exception ex)
{
_logger.Warn("键 {Key} 的值类型转换失败: {Message},使用默认值 | Value type conversion failed for key {Key}: {Message}, using default", key, ex.Message);
return defaultValue;
}
}
/// <summary>
/// 从字典中获取列表值 | Get list value from dictionary
/// </summary>
private IList GetListValue(Dictionary<string, object> data, string key)
{
if (data == null || !data.TryGetValue(key, out var value) || value == null)
{
_logger.Warn("处理器输出缺少列表键: {Key},返回空列表 | Processor output missing list key: {Key}, returning empty list", key);
return null;
}
if (value is IList list)
{
return list;
}
_logger.Warn("键 {Key} 的值不是列表类型 | Value for key {Key} is not a list type", key);
return null;
}
/// <summary>
/// 从嵌套对象中获取属性值 | Get property value from nested object
/// </summary>
private T GetNestedValue<T>(object obj, string propertyName, T defaultValue)
{
if (obj == null) return defaultValue;
// 字典访问 | Dictionary access
if (obj is IDictionary<string, object> dict)
{
if (dict.TryGetValue(propertyName, out var dictValue) && dictValue != null)
{
try
{
return ConvertValue<T>(dictValue);
}
catch
{
return defaultValue;
}
}
return defaultValue;
}
// 反射访问 | Reflection access
var type = obj.GetType();
var propInfo = type.GetProperty(propertyName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase);
if (propInfo != null)
{
try
{
var value = propInfo.GetValue(obj);
if (value == null) return defaultValue;
return ConvertValue<T>(value);
}
catch
{
return defaultValue;
}
}
return defaultValue;
}
/// <summary>
/// 类型转换辅助方法 | Type conversion helper
/// </summary>
private T ConvertValue<T>(object value)
{
if (value == null) return default;
var targetType = typeof(T);
// 直接类型匹配 | Direct type match
if (value is T typedValue)
{
return typedValue;
}
// 处理 nullable 类型 | Handle nullable types
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
// object 类型直接返回 | Return directly for object type
if (underlyingType == typeof(object))
{
return (T)value;
}
// 数值类型转换 | Numeric type conversion
if (underlyingType == typeof(double))
{
return (T)(object)Convert.ToDouble(value);
}
if (underlyingType == typeof(int))
{
return (T)(object)Convert.ToInt32(value);
}
if (underlyingType == typeof(float))
{
return (T)(object)Convert.ToSingle(value);
}
if (underlyingType == typeof(long))
{
return (T)(object)Convert.ToInt64(value);
}
// 字符串转换 | String conversion
if (underlyingType == typeof(string))
{
return (T)(object)value.ToString();
}
// 通用转换 | General conversion
return (T)Convert.ChangeType(value, underlyingType);
}
/// <summary>
/// 将 PascalCase 转换为 camelCase | Convert PascalCase to camelCase
/// </summary>
private string ToCamelCase(string input)
{
if (string.IsNullOrEmpty(input)) return input;
if (input.Length == 1) return input.ToLowerInvariant();
return char.ToLowerInvariant(input[0]) + input.Substring(1);
}
/// <summary>
/// 根据处理器类型获取模板中对应的图像 dataKey | Get template image dataKey by processor type
/// </summary>
private static string GetImageDataKey(string processorType)
{
return processorType switch
{
LineMeasurementProcessor => "lineMeasurementImage",
BgaVoidRateProcessor => "bgaInspectionImage",
VoidMeasurementProcessor => "voidInspectionImage",
FillRateProcessor => "viaFillImage",
_ => $"{processorType}_image"
};
}
/// <summary>
/// 根据处理器类型获取模板中对应的表格 dataKey | Get template table dataKey by processor type
/// </summary>
private static string GetTableDataKey(string processorType)
{
return processorType switch
{
BgaVoidRateProcessor => "bgaBallsTable",
VoidMeasurementProcessor => "voidsTable",
_ => null
};
}
#endregion
}
}