VisionStaticAccuracyAnalyzerViewModel.cs 50 KB

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