VisionStaticAccuracyAnalyzerViewModel.cs 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using MaterialDesignThemes.Wpf;
  4. using Prism.Commands;
  5. using Prism.Events;
  6. using Prism.Ioc;
  7. using Prism.Mvvm;
  8. using Prism.Regions;
  9. using Prism.Services.Dialogs;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.ObjectModel;
  13. using System.Data;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. using Team.FFFeederService;
  19. using Team.FFFeederService.Interfaces;
  20. using TeamAAS_VP.Core;
  21. using TeamAAS_VP.Data;
  22. using TeamAAS_VP.Enums;
  23. using TeamAAS_VP.Events;
  24. using TeamAAS_VP.Interfaces;
  25. using TeamAAS_VP.Models;
  26. using TeamAAS_VP.Models.Calibration;
  27. using TeamAAS_VP.Resources.Languages;
  28. using TeamAAS_VP.Services;
  29. using TeamAAS_VP.Views.Setting;
  30. using static TeamAAS_VP.Core.StabilityAnalyzer;
  31. namespace TeamAAS_VP.ViewModels.Product
  32. {
  33. public class VisionStaticAccuracyAnalyzerViewModel : BindableBase, IDialogAware
  34. {
  35. IRegionManager _regionManager;
  36. IEventAggregator _eventAggregator;
  37. IContainerProvider _container;
  38. IDialogService _dialogService;
  39. IFeederService _feederService;
  40. IRobotService _robotService;
  41. ICameraService _cameraService;
  42. ISystemDatabaseService _systemDatabaseService;
  43. ICalibrationService _calibrationService;
  44. //测试过程中控制取消的CTS
  45. CancellationTokenSource _cts;
  46. #region 属性
  47. private ICogImage _Image;
  48. public ICogImage Image
  49. {
  50. get { return _Image; }
  51. set { SetProperty(ref _Image, value); }
  52. }
  53. private Cognex.VisionPro.CogGraphicCollection _Graphic;
  54. public Cognex.VisionPro.CogGraphicCollection Graphic
  55. {
  56. get { return _Graphic; }
  57. set { SetProperty(ref _Graphic, value); }
  58. }
  59. private ProcedureModel _SelectProcedure;
  60. /// <summary>
  61. /// 选中的流程
  62. /// </summary>
  63. public ProcedureModel SelectProcedure
  64. {
  65. get { return _SelectProcedure; }
  66. set
  67. {
  68. SetProperty(ref _SelectProcedure, value);
  69. }
  70. }
  71. private ICamera _Camera;
  72. public ICamera Camera
  73. {
  74. get { return _Camera; }
  75. set { SetProperty(ref _Camera, value); }
  76. }
  77. private string _Message = "等待开始...";
  78. public string Message
  79. {
  80. get { return _Message; }
  81. set { SetProperty(ref _Message, value); }
  82. }
  83. private bool _IsRunning;
  84. public bool IsRunning
  85. {
  86. get { return _IsRunning; }
  87. set { SetProperty(ref _IsRunning, value); }
  88. }
  89. private CogToolBlock _VisionTool;
  90. public CogToolBlock VisionTool
  91. {
  92. get { return _VisionTool; }
  93. set { SetProperty(ref _VisionTool, value); }
  94. }
  95. private int _RepeatCount = 10;
  96. /// <summary>
  97. /// 重复次数
  98. /// </summary>
  99. public int RepeatCount
  100. {
  101. get { return _RepeatCount; }
  102. set { SetProperty(ref _RepeatCount, value); }
  103. }
  104. private int _CurrentProgress;
  105. /// <summary>
  106. /// 当前进度
  107. /// </summary>
  108. public int CurrentProgress
  109. {
  110. get { return _CurrentProgress; }
  111. set { SetProperty(ref _CurrentProgress, value); }
  112. }
  113. private int _SuccessCount;
  114. /// <summary>
  115. /// 成功次数
  116. /// </summary>
  117. public int SuccessCount
  118. {
  119. get { return _SuccessCount; }
  120. set { SetProperty(ref _SuccessCount, value); }
  121. }
  122. private int _FailCount;
  123. /// <summary>
  124. /// 失败次数
  125. /// </summary>
  126. public int FailCount
  127. {
  128. get { return _FailCount; }
  129. set { SetProperty(ref _FailCount, value); }
  130. }
  131. private double _SuccessRate;
  132. /// <summary>
  133. /// 成功率
  134. /// </summary>
  135. public double SuccessRate
  136. {
  137. get { return _SuccessRate; }
  138. set { SetProperty(ref _SuccessRate, value); }
  139. }
  140. private DataTable _TestResult;
  141. /// <summary>
  142. /// 测试结果列表DataTable
  143. /// </summary>
  144. public DataTable TestResult
  145. {
  146. get { return _TestResult; }
  147. set { SetProperty(ref _TestResult, value); }
  148. }
  149. private ObservableCollection<DataColumnInfo> _ResultDataColumns;
  150. /// <summary>
  151. /// DataTable的列集合
  152. /// </summary>
  153. public ObservableCollection<DataColumnInfo> ResultDataColumns
  154. {
  155. get { return _ResultDataColumns; }
  156. set { SetProperty(ref _ResultDataColumns, value); }
  157. }
  158. //
  159. private DataColumnInfo _SelectedDataColumn;
  160. /// <summary>
  161. /// 选中的列
  162. /// </summary>
  163. public DataColumnInfo SelectedDataColumn
  164. {
  165. get { return _SelectedDataColumn; }
  166. set { SetProperty(ref _SelectedDataColumn, value); }
  167. }
  168. private SnackbarMessageQueue _MessageQueue;
  169. public SnackbarMessageQueue MessageQueue
  170. {
  171. get { return _MessageQueue; }
  172. set { SetProperty(ref _MessageQueue, value); }
  173. }
  174. //选中列分析结果
  175. private StabilityMetrics _StabilityMetrics;
  176. public StabilityMetrics StabilityMetrics
  177. {
  178. get { return _StabilityMetrics; }
  179. set { SetProperty(ref _StabilityMetrics, value); }
  180. }
  181. #endregion
  182. #region 命令
  183. private DelegateCommand _ConfirmCommand;
  184. public DelegateCommand ConfirmCommand =>
  185. _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  186. private DelegateCommand _CancelCommand;
  187. public DelegateCommand CancelCommand =>
  188. _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  189. private DelegateCommand _StartTestCommand;
  190. public DelegateCommand StartTestCommand =>
  191. _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning));
  192. private DelegateCommand _StopTestCommand;
  193. public DelegateCommand StopTestCommand =>
  194. _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  195. //暂停测试命令
  196. private DelegateCommand _PauseTestCommand;
  197. public DelegateCommand PauseTestCommand =>
  198. _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  199. private DelegateCommand _ExportCSVCommand;
  200. public DelegateCommand ExportCSVCommand =>
  201. _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  202. private DelegateCommand _ExportReportCommand;
  203. public DelegateCommand ExportReportCommand =>
  204. _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  205. private DelegateCommand _ClearDataCommand;
  206. public DelegateCommand ClearDataCommand =>
  207. _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  208. private DelegateCommand _SelectColumnCommand;
  209. public DelegateCommand SelectColumnCommand =>
  210. _SelectColumnCommand ?? (_SelectColumnCommand = new DelegateCommand(ExecuteSelectColumnCommand));
  211. #endregion
  212. #region 事件
  213. #endregion
  214. public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
  215. IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService)
  216. {
  217. _regionManager = regionManager;
  218. _eventAggregator = ea;
  219. _container = container;
  220. _dialogService = dialogService;
  221. _feederService = feederService;
  222. _robotService = robotService;
  223. _cameraService = cameraService;
  224. _systemDatabaseService = systemDatabaseService;
  225. MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
  226. _calibrationService = calibrationService;
  227. }
  228. #region 方法
  229. /// <summary>
  230. /// 确定
  231. /// </summary>
  232. void ExecuteConfirmCommand()
  233. {
  234. _cts?.Cancel();
  235. IDialogParameters parameters = new DialogParameters();
  236. //parameters.Add("Tool", Tool);
  237. //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
  238. //parameters.Add("Angle", Angle);
  239. RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
  240. }
  241. /// <summary>
  242. /// 取消
  243. /// </summary>
  244. void ExecuteCancelCommand()
  245. {
  246. _cts?.Cancel();
  247. IDialogParameters parameters = new DialogParameters();
  248. //parameters.Add("Tool", Tool);
  249. RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
  250. }
  251. /// <summary>
  252. /// 开始测试
  253. /// </summary>
  254. async void ExecuteStartTestCommand()
  255. {
  256. IsRunning = true;
  257. _cts = new CancellationTokenSource();
  258. CurrentProgress = 0;
  259. SuccessCount = 0;
  260. SuccessRate = 0;
  261. FailCount = 0;
  262. Message = "测试进行中...";
  263. try
  264. {
  265. while (CurrentProgress < RepeatCount)
  266. {
  267. if (_cts.Token.IsCancellationRequested)
  268. {
  269. Message = "测试已取消!";
  270. break;
  271. }
  272. (bool isSuccess, object[] Result) = await ExecutePhotoEx(CurrentProgress);
  273. if (isSuccess)
  274. {
  275. SuccessCount++;
  276. }
  277. else
  278. {
  279. FailCount++;
  280. }
  281. TestResult.Rows.Add(Result);
  282. ExecuteSelectColumnCommand();
  283. CurrentProgress++;
  284. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  285. SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
  286. }
  287. if (CurrentProgress >= RepeatCount)
  288. {
  289. Message = "测试完成!";
  290. }
  291. }
  292. catch (Exception ex)
  293. {
  294. LogHelper.WriteLogError("视觉静态重复测试时出错!", ex);
  295. }
  296. finally
  297. {
  298. IsRunning = false;
  299. }
  300. }
  301. bool CanExecuteStartTestCommand()
  302. {
  303. return !IsRunning;
  304. }
  305. /// <summary>
  306. /// 清除数据
  307. /// </summary>
  308. void ExecuteClearDataCommand()
  309. {
  310. if(TestResult!=null)
  311. {
  312. TestResult.Rows.Clear();
  313. }
  314. }
  315. /// <summary>
  316. /// 导出测试报告
  317. /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
  318. /// 详细伪代码:
  319. /// 1. 校验 TestResult 是否存在数据,若无则提示并返回。
  320. /// 2. 弹出保存对话框,获取保存路径,若取消返回。
  321. /// 3. 创建 iTextSharp Document 与 PdfWriter,打开文档。
  322. /// 4. 创建支持中文的 BaseFont(例如 STSongStd-Light + UniGB-UCS2-H)。
  323. /// 5. 写入标题(居中、大号字体)。
  324. /// 6. 写入测试摘要信息(测试时间、重复次数、成功/失败/成功率等),每项为单独段落或表格。
  325. /// 7. 构建 PdfPTable:列数 = TestResult.Columns.Count,写入表头(加粗),逐行写入数据:
  326. /// - 对于数值使用 InvariantCulture 格式化,空值写空字符串。
  327. /// 8. 写入分析结果标题。
  328. /// 9. 对于 ResultDataColumns 中的每列:
  329. /// - 从 TestResult 读取该列所有数值(尝试转换为 double,忽略无法转换的项)。
  330. /// - 如果样本数为 0,写入“样本数为0”提示并跳过。
  331. /// - 调用 StabilityAnalyzer.AnalyzeStability(values) 获取指标对象。
  332. /// - 遍历指标对象的公开属性,将属性名与值写入一个两列的 PdfPTable(或段落)。
  333. /// 10. 关闭文档并释放资源。
  334. /// 11. 捕获异常并记录日志,提示用户失败信息。
  335. /// </summary>
  336. void ExecuteExportReportCommand()
  337. {
  338. try
  339. {
  340. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  341. {
  342. SendTaskMessage("无测试数据,无法导出报告。");
  343. return;
  344. }
  345. var dlg = new Microsoft.Win32.SaveFileDialog
  346. {
  347. DefaultExt = "pdf",
  348. Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
  349. FileName = "TestReport.pdf",
  350. Title = "保存测试报告为 PDF"
  351. };
  352. bool? dlgResult = dlg.ShowDialog();
  353. if (dlgResult != true)
  354. return;
  355. string path = dlg.FileName;
  356. // 创建文档(A4, 边距)
  357. var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
  358. using (var fs = System.IO.File.Create(path))
  359. {
  360. var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
  361. doc.Open();
  362. // --- 字体加载:优先载入系统中文字体并以 IDENTITY_H 编码嵌入,保证中文显示 ---
  363. iTextSharp.text.Font titleFont;
  364. iTextSharp.text.Font headerFont;
  365. iTextSharp.text.Font normalFont;
  366. iTextSharp.text.pdf.BaseFont baseFont = null;
  367. try
  368. {
  369. var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
  370. var candidates = new[]
  371. {
  372. "msyh.ttf",
  373. "msyhbd.ttf",
  374. "simsun.ttc,0", // 添加 ",0" 指定第一个字体
  375. "simsun.ttc,1", // 第二个字体
  376. "Microsoft YaHei.ttf",
  377. "msyh.ttc,0", // 添加 ",0"
  378. "msyh.ttc,1", // 添加 ",1"
  379. "simhei.ttf"
  380. };
  381. foreach (var f in candidates)
  382. {
  383. var path1 = Path.Combine(fontsFolder, f);
  384. try
  385. {
  386. baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
  387. path1,
  388. iTextSharp.text.pdf.BaseFont.IDENTITY_H,
  389. iTextSharp.text.pdf.BaseFont.EMBEDDED
  390. );
  391. if (baseFont != null)
  392. {
  393. Console.WriteLine($"成功加载字体: {f}");
  394. break;
  395. }
  396. }
  397. catch (Exception ex)
  398. {
  399. Console.WriteLine($"字体 {f} 加载失败: {ex.Message}");
  400. continue;
  401. }
  402. }
  403. }
  404. catch
  405. {
  406. baseFont = null;
  407. }
  408. if (baseFont != null)
  409. {
  410. titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  411. headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  412. normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  413. }
  414. else
  415. {
  416. // 回退:如果未找到系统中文字体,使用内置 Helvetica(注意:可能无法正确显示中文)
  417. titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  418. headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  419. normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  420. }
  421. // 标题
  422. var title = new iTextSharp.text.Paragraph("视觉静态精度测试报告", titleFont)
  423. {
  424. Alignment = iTextSharp.text.Element.ALIGN_CENTER,
  425. SpacingAfter = 12f
  426. };
  427. doc.Add(title);
  428. // 测试摘要
  429. var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
  430. metaTable.SetWidths(new float[] { 1f, 2f });
  431. void AddMeta(string name, string value)
  432. {
  433. var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
  434. var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
  435. metaTable.AddCell(cellName);
  436. metaTable.AddCell(cellVal);
  437. }
  438. AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  439. AddMeta("测试次数(目标)", RepeatCount.ToString());
  440. AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
  441. AddMeta("成功次数", SuccessCount.ToString());
  442. AddMeta("失败次数", FailCount.ToString());
  443. AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
  444. doc.Add(metaTable);
  445. doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
  446. // 数据表格
  447. int colCount = TestResult.Columns.Count;
  448. var table = new iTextSharp.text.pdf.PdfPTable(colCount) { WidthPercentage = 100f };
  449. // 表头
  450. foreach (System.Data.DataColumn col in TestResult.Columns)
  451. {
  452. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(col.ColumnName, headerFont))
  453. {
  454. HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
  455. BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
  456. Padding = 4f
  457. };
  458. table.AddCell(cell);
  459. }
  460. // 数据行
  461. foreach (System.Data.DataRow row in TestResult.Rows)
  462. {
  463. for (int c = 0; c < colCount; c++)
  464. {
  465. object val = row[c];
  466. string s;
  467. if (val == null || val == DBNull.Value)
  468. s = "";
  469. else if (val is double || val is float || val is decimal)
  470. s = Convert.ToDouble(val).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  471. else
  472. s = val.ToString();
  473. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(s, normalFont)) { Padding = 4f };
  474. table.AddCell(cell);
  475. }
  476. }
  477. doc.Add(table);
  478. doc.NewPage();
  479. // 分析结果
  480. var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
  481. doc.Add(analysisTitle);
  482. if (ResultDataColumns != null && ResultDataColumns.Count > 0)
  483. {
  484. foreach (var colInfo in ResultDataColumns)
  485. {
  486. try
  487. {
  488. // 收集数值
  489. var values = new System.Collections.Generic.List<double>();
  490. foreach (System.Data.DataRow row in TestResult.Rows)
  491. {
  492. object v = row[colInfo.ColumnIndex];
  493. if (v == null || v == DBNull.Value) continue;
  494. double d;
  495. if (v is double) d = (double)v;
  496. else if (v is float) d = Convert.ToDouble((float)v);
  497. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  498. else if (v is int) d = Convert.ToDouble((int)v);
  499. else if (v is long) d = Convert.ToDouble((long)v);
  500. else
  501. {
  502. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  503. continue;
  504. }
  505. values.Add(d);
  506. }
  507. var colHeader = new iTextSharp.text.Paragraph(colInfo.ColumnName, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
  508. doc.Add(colHeader);
  509. if (values.Count == 0)
  510. {
  511. doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
  512. continue;
  513. }
  514. // 计算稳定性指标
  515. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  516. // 将指标写成两列表(属性名 / 值)
  517. var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
  518. metricsTable.SetWidths(new float[] { 1f, 1f });
  519. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  520. foreach (var p in props)
  521. {
  522. object pv = p.GetValue(metrics);
  523. string pvStr;
  524. if (pv == null) pvStr = "";
  525. else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  526. else pvStr = pv.ToString();
  527. var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
  528. var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
  529. metricsTable.AddCell(pc);
  530. metricsTable.AddCell(vc);
  531. }
  532. doc.Add(metricsTable);
  533. }
  534. catch (Exception exCol)
  535. {
  536. LogHelper.WriteLogError($"导出报告时处理列[{colInfo.ColumnName}]出错", exCol);
  537. doc.Add(new iTextSharp.text.Paragraph($"处理列 {colInfo.ColumnName} 时出错: {exCol.Message}", normalFont));
  538. }
  539. }
  540. }
  541. else
  542. {
  543. doc.Add(new iTextSharp.text.Paragraph("无用于分析的数值列。", normalFont));
  544. }
  545. doc.Close();
  546. writer.Close();
  547. }
  548. SendTaskMessage($"已导出测试报告到:{path}");
  549. }
  550. catch (Exception ex)
  551. {
  552. LogHelper.WriteLogError("导出测试报告时出错", ex);
  553. SendTaskMessage($"导出测试报告失败:{ex.Message}");
  554. }
  555. }
  556. /// <summary>
  557. /// 导出测试数据为CSV文件
  558. /// </summary>
  559. void ExecuteExportCSVCommand()
  560. {
  561. try
  562. {
  563. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  564. {
  565. SendTaskMessage("无测试数据,无法导出。");
  566. return;
  567. }
  568. var dlg = new Microsoft.Win32.SaveFileDialog
  569. {
  570. DefaultExt = "csv",
  571. Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
  572. FileName = "TestResult.csv",
  573. Title = "保存测试结果为 CSV"
  574. };
  575. bool? dlgResult = dlg.ShowDialog();
  576. if (dlgResult != true)
  577. return;
  578. string path = dlg.FileName;
  579. var sb = new System.Text.StringBuilder();
  580. // 辅助:CSV 字段转义
  581. Func<string, string> EscapeCsv = (s) =>
  582. {
  583. if (s == null) return "";
  584. bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
  585. string esc = s.Replace("\"", "\"\"");
  586. return mustQuote ? $"\"{esc}\"" : esc;
  587. };
  588. // 1. 写入表头
  589. for (int c = 0; c < TestResult.Columns.Count; c++)
  590. {
  591. if (c > 0) sb.Append(",");
  592. sb.Append(EscapeCsv(TestResult.Columns[c].ColumnName));
  593. }
  594. sb.AppendLine();
  595. // 2. 写入数据行
  596. foreach (System.Data.DataRow row in TestResult.Rows)
  597. {
  598. for (int c = 0; c < TestResult.Columns.Count; c++)
  599. {
  600. if (c > 0) sb.Append(",");
  601. object val = row[c];
  602. if (val == DBNull.Value || val == null)
  603. {
  604. sb.Append("");
  605. }
  606. else
  607. {
  608. // 保持数值格式,其他转为字符串
  609. string outStr;
  610. if (val is double || val is float || val is decimal)
  611. outStr = Convert.ToString(val, System.Globalization.CultureInfo.InvariantCulture);
  612. else if (val is int || val is long || val is short || val is byte)
  613. outStr = val.ToString();
  614. else if (val is bool)
  615. outStr = (bool)val ? "True" : "False";
  616. else
  617. outStr = val.ToString();
  618. sb.Append(EscapeCsv(outStr));
  619. }
  620. }
  621. sb.AppendLine();
  622. }
  623. // 3. 在末尾追加分析结果
  624. sb.AppendLine(); // 空行
  625. sb.AppendLine("分析结果");
  626. sb.AppendLine("列名,指标,值"); // CSV 表头:列名,指标,值
  627. if (ResultDataColumns != null)
  628. {
  629. foreach (var colInfo in ResultDataColumns)
  630. {
  631. try
  632. {
  633. List<double> values = new List<double>();
  634. foreach (System.Data.DataRow row in TestResult.Rows)
  635. {
  636. object v = row[colInfo.ColumnIndex];
  637. if (v != DBNull.Value && v != null)
  638. {
  639. double d;
  640. // 支持不同数字类型
  641. if (v is double) d = (double)v;
  642. else if (v is float) d = Convert.ToDouble((float)v);
  643. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  644. else if (v is int) d = Convert.ToDouble((int)v);
  645. else if (v is long) d = Convert.ToDouble((long)v);
  646. else
  647. {
  648. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  649. continue;
  650. }
  651. values.Add(d);
  652. }
  653. }
  654. if (values.Count == 0)
  655. {
  656. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("样本数")},{0}");
  657. continue;
  658. }
  659. // 调用稳定性分析
  660. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  661. // 使用反射枚举 metrics 的公开属性并写入 CSV
  662. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  663. foreach (var p in props)
  664. {
  665. object pv = p.GetValue(metrics);
  666. string pvStr = pv == null ? "" : (pv is double ? Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture) : pv.ToString());
  667. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
  668. }
  669. }
  670. catch (Exception exCol)
  671. {
  672. LogHelper.WriteLogError($"导出分析结果时处理列[{colInfo.ColumnName}]出错", exCol);
  673. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
  674. }
  675. }
  676. }
  677. // 写入文件(UTF8,无 BOM,若需 BOM 可使用 new UTF8Encoding(true))
  678. System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
  679. SendTaskMessage($"已导出测试结果到:{path}");
  680. }
  681. catch (Exception ex)
  682. {
  683. LogHelper.WriteLogError("导出CSV时出错", ex);
  684. SendTaskMessage($"导出CSV失败:{ex.Message}");
  685. }
  686. }
  687. /// <summary>
  688. /// 选中列时触发
  689. /// </summary>
  690. void ExecuteSelectColumnCommand()
  691. {
  692. try
  693. {
  694. //如果选中列为空,则直接返回
  695. if (SelectedDataColumn == null)
  696. return;
  697. //获取表的指定列的所有数据集合
  698. List<double> dataList = new List<double>();
  699. foreach (DataRow row in TestResult.Rows)
  700. {
  701. if (row[SelectedDataColumn.ColumnIndex] != DBNull.Value)
  702. {
  703. dataList.Add(Convert.ToDouble(row[SelectedDataColumn.ColumnIndex]));
  704. }
  705. }
  706. //如果数据量小于10,则不进行分析
  707. if (dataList.Count < 10)
  708. {
  709. //MessageQueue.Enqueue("选中列的数据量小于10,无法进行统计分析!");
  710. return;
  711. }
  712. //进行稳定性分析
  713. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dataList.ToArray());
  714. App.Current.Dispatcher.Invoke(() =>
  715. {
  716. StabilityMetrics = metrics;
  717. });
  718. }
  719. catch (Exception ex)
  720. {
  721. LogHelper.WriteLogError("选中列的数据分析数据稳定性时出错!", ex);
  722. }
  723. }
  724. /// <summary>
  725. /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
  726. /// </summary>
  727. /// <param name="index"></param>
  728. /// <returns></returns>
  729. public Task<(bool isSuccess, object[] Result)> ExecutePhotoEx(int index)
  730. {
  731. return Task.Run(() =>
  732. {
  733. CogToolBlockTerminalCollection outputCollection;
  734. List<object> result = new List<object>();
  735. result.Add(index);
  736. try
  737. {
  738. // 1. 采集图像
  739. bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
  740. if (!succed)
  741. {
  742. SendTaskMessage(Lang.相机曝光设置失败);
  743. }
  744. succed = Camera.SetGain(SelectProcedure.Gain);
  745. if (!succed)
  746. {
  747. SendTaskMessage(Lang.相机增益设置失败);
  748. }
  749. DateTime nowtime = DateTime.Now;
  750. LogHelper.WriteLogInfo(Lang.开始采集图像);
  751. var image = Camera.Grab();
  752. if (image == null)
  753. {
  754. SendTaskMessage(Lang.图像采集失败);
  755. outputCollection = null;
  756. return (false, result.ToArray());
  757. }
  758. //Image = image;
  759. VisionTool.Inputs["InputImage"].Value = image;
  760. LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
  761. // 2. 为ToolBlock传入其他输入终端
  762. //if (InputTerminal != null)
  763. //{
  764. // LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
  765. // foreach (var item in InputTerminal)
  766. // {
  767. // if (VisionTool.Inputs.Contains(item.Key))
  768. // {
  769. // LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
  770. // VisionTool.Inputs[item.Key].Value = item.Value;
  771. // }
  772. // else
  773. // {
  774. // LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
  775. // VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
  776. // }
  777. // }
  778. //}
  779. Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
  780. // 3. 运行视觉工具
  781. LogHelper.WriteLogInfo(Lang.开始运行视觉工具);
  782. VisionTool.Run(); //运行ToolBlock
  783. if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
  784. {
  785. SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  786. outputCollection = VisionTool.Outputs; //获取输出终端集合
  787. foreach (CogToolBlockTerminal terminal in outputCollection)
  788. {
  789. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  790. if (terminal.ValueType == typeof(string))
  791. {
  792. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  793. if (terminal.Name == "Point")
  794. {
  795. //是否需要转换为绝对坐标
  796. needConvertToAbsolute = false;
  797. //如果当前流程的校准为空,则不需要转换为绝对坐标
  798. if (SelectProcedure.CalibrationId != Guid.Empty)
  799. {
  800. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  801. if (calibration != null)
  802. {
  803. needConvertToAbsolute = true;
  804. }
  805. }
  806. //判断是否是点位格式
  807. string strpoints = terminal.Value.ToString();
  808. string[] items = strpoints.Split(';');
  809. bool isPointFormat = true;
  810. foreach (string item in items)
  811. {
  812. string[] subitems = item.Split(',');
  813. if (subitems.Length < 3)
  814. {
  815. isPointFormat = false;
  816. break;
  817. }
  818. double x, y, u;
  819. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  820. {
  821. isPointFormat = false;
  822. break;
  823. }
  824. }
  825. if (isPointFormat)
  826. {
  827. //是点位格式,则创建多列
  828. for (int i = 0; i < items.Length; i++)
  829. {
  830. string[] subitems = items[i].Split(',');
  831. //先分别创建像素X、像素Y、角度U三列
  832. result.Add(double.Parse(subitems[0]));
  833. result.Add(double.Parse(subitems[1]));
  834. result.Add(double.Parse(subitems[2]));
  835. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  836. if (needConvertToAbsolute)
  837. {
  838. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  839. (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), null, calibration);
  840. result.Add(Math.Round(X, 5));
  841. result.Add(Math.Round(Y, 5));
  842. }
  843. }
  844. }
  845. else
  846. {
  847. result.Add(terminal.Value.ToString());
  848. }
  849. }
  850. else
  851. {
  852. result.Add(terminal.Value.ToString());
  853. }
  854. }
  855. else if (terminal.ValueType == typeof(int))
  856. result.Add((int)terminal.Value);
  857. else if (terminal.ValueType == typeof(double))
  858. result.Add((double)terminal.Value);
  859. else if (terminal.ValueType == typeof(bool))
  860. result.Add((bool)terminal.Value);
  861. }
  862. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  863. return (true, result.ToArray());
  864. }
  865. else
  866. {
  867. SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  868. SendTaskMessage(VisionTool.RunStatus.Message);
  869. outputCollection = null;
  870. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  871. return (false, result.ToArray());
  872. }
  873. }
  874. catch (Exception ex)
  875. {
  876. SendTaskMessage(ex.Message);
  877. LogHelper.WriteLogError(Lang.执行相机取图并执行视觉工具组时出错, ex);
  878. outputCollection = null;
  879. return (false, result.ToArray());
  880. }
  881. });
  882. }
  883. public void SendTaskMessage(string msg)
  884. {
  885. App.Current.Dispatcher.Invoke(() =>
  886. {
  887. MessageQueue.Clear();
  888. MessageQueue.Enqueue(msg);
  889. });
  890. }
  891. bool needConvertToAbsolute = false;
  892. /// <summary>
  893. /// 根据VisionTool的输出终端,创建DataTable的列
  894. /// </summary>
  895. /// <param name="outputCollection"></param>
  896. /// <returns></returns>
  897. private DataTable CreateResultDataTable(CogToolBlockTerminalCollection outputCollection)
  898. {
  899. DataTable dt = new DataTable();
  900. ResultDataColumns = new ObservableCollection<DataColumnInfo>();
  901. //第一列添加序号
  902. dt.Columns.Add("序号", typeof(int));
  903. //列索引
  904. foreach (CogToolBlockTerminal terminal in outputCollection)
  905. {
  906. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  907. if (terminal.ValueType == typeof(string))
  908. {
  909. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  910. if (terminal.Name == "Point")
  911. {
  912. //是否需要转换为绝对坐标
  913. needConvertToAbsolute = false;
  914. //如果当前流程的校准为空,则不需要转换为绝对坐标
  915. if (SelectProcedure.CalibrationId != Guid.Empty)
  916. {
  917. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  918. if (calibration != null)
  919. {
  920. needConvertToAbsolute = true;
  921. }
  922. }
  923. //判断是否是点位格式
  924. string strpoints = terminal.Value.ToString();
  925. string[] items = strpoints.Split(';');
  926. bool isPointFormat = true;
  927. foreach (string item in items)
  928. {
  929. string[] subitems = item.Split(',');
  930. if (subitems.Length < 3)
  931. {
  932. isPointFormat = false;
  933. break;
  934. }
  935. double x, y, u;
  936. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  937. {
  938. isPointFormat = false;
  939. break;
  940. }
  941. }
  942. if (isPointFormat)
  943. {
  944. //是点位格式,则创建多列
  945. for (int i = 0; i < items.Length; i++)
  946. {
  947. //先分别创建像素X、像素Y、角度U三列
  948. dt.Columns.Add($"{terminal.Name}_Pixel_X{i + 1}", typeof(double));
  949. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_X{i + 1}", dt.Columns.Count - 1));
  950. dt.Columns.Add($"{terminal.Name}_Pixel_Y{i + 1}", typeof(double));
  951. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_Y{i + 1}", dt.Columns.Count - 1));
  952. dt.Columns.Add($"{terminal.Name}_Angle_U{i + 1}", typeof(double));
  953. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Angle_U{i + 1}", dt.Columns.Count - 1));
  954. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  955. if (needConvertToAbsolute)
  956. {
  957. dt.Columns.Add($"{terminal.Name}_Absolute_X{i + 1}", typeof(double));
  958. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_X{i + 1}", dt.Columns.Count - 1));
  959. dt.Columns.Add($"{terminal.Name}_Absolute_Y{i + 1}", typeof(double));
  960. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_Y{i + 1}", dt.Columns.Count - 1));
  961. }
  962. }
  963. }
  964. else
  965. {
  966. dt.Columns.Add(terminal.Name, typeof(string));
  967. }
  968. }
  969. else
  970. {
  971. dt.Columns.Add(terminal.Name, typeof(string));
  972. }
  973. }
  974. else if (terminal.ValueType == typeof(int))
  975. {
  976. dt.Columns.Add(terminal.Name, typeof(int));
  977. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  978. }
  979. else if (terminal.ValueType == typeof(double))
  980. {
  981. dt.Columns.Add(terminal.Name, typeof(double));
  982. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  983. }
  984. else if (terminal.ValueType == typeof(bool))
  985. {
  986. dt.Columns.Add(terminal.Name, typeof(bool));
  987. }
  988. //else
  989. // dt.Columns.Add(terminal.Name, typeof(string));
  990. }
  991. return dt;
  992. }
  993. #endregion
  994. #region 继承
  995. public string Title { get; set; } = "视觉静态精度分析仪";
  996. public event Action<IDialogResult> RequestClose;
  997. public bool CanCloseDialog()
  998. {
  999. return true;
  1000. }
  1001. public void OnDialogClosed()
  1002. {
  1003. //关闭时,停止测试
  1004. _cts?.Cancel();
  1005. }
  1006. public void OnDialogOpened(IDialogParameters parameters)
  1007. {
  1008. SelectProcedure = parameters.GetValue<ProcedureModel>("SelectedProcedure");
  1009. Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
  1010. VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
  1011. VisionTool.Run();
  1012. TestResult = CreateResultDataTable(VisionTool.Outputs);
  1013. }
  1014. #endregion
  1015. }
  1016. //辅助类,用于定义DataTable的列,仅用来对于数值列进行统计分析
  1017. public class DataColumnInfo
  1018. {
  1019. public string ColumnName { get; set; }
  1020. //列索引
  1021. public int ColumnIndex { get; set; }
  1022. public DataColumnInfo(string columnName, int columnIndex)
  1023. {
  1024. ColumnName = columnName;
  1025. ColumnIndex = columnIndex;
  1026. }
  1027. }
  1028. }