auto_doppler_trace.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // ignore_for_file: invalid_use_of_protected_member
  2. import 'dart:ui';
  3. import 'package:fis_measure/interfaces/date_types/point.dart';
  4. import 'package:fis_measure/interfaces/enums/items.dart';
  5. import 'package:fis_measure/interfaces/process/items/item.dart';
  6. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
  8. import 'package:fis_measure/process/calcuators/auto_doppler_trace.dart';
  9. import 'package:fis_measure/process/items/item.dart';
  10. import 'package:fis_measure/process/items/item_feature.dart';
  11. import 'package:fis_measure/process/physical_coordinates/doppler.dart';
  12. import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/cardiac_cycle.dart';
  13. import 'package:fis_measure/process/primitives/multi_method/dop_trace_disp/data.dart';
  14. import 'package:fis_measure/utils/canvas.dart';
  15. import 'package:fis_measure/utils/prompt_box.dart';
  16. import 'package:fis_measure/view/gesture/positioned_touch_cursor.dart';
  17. import 'package:flutter/material.dart';
  18. import 'package:get/get.dart';
  19. class AutoDopplerTrace extends TraceItemAbstract {
  20. AutoDopplerTrace(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);
  21. late final touchState = Get.find<ITouchPointState>();
  22. @override
  23. bool onExecuteMouse(PointInfo args) {
  24. if (state == ItemStates.finished) {
  25. if (args.pointType == PointInfoType.mouseDown) {
  26. state = ItemStates.waiting;
  27. }
  28. }
  29. if (state == ItemStates.waiting) {
  30. if (args.pointType == PointInfoType.mouseDown) {
  31. handleMouseDownWhileWaiting(args);
  32. handleFinish();
  33. } else if (state == ItemStates.running) {
  34. if (args.pointType == PointInfoType.mouseUp) return false;
  35. }
  36. }
  37. return true;
  38. }
  39. void handleFinish() async {
  40. await Future.delayed(const Duration(milliseconds: 50));
  41. List<CardiacCycle> currentCardiacCycleList =
  42. feature?.refItem.currentCardiacCycleList ?? [];
  43. print(currentCardiacCycleList.length);
  44. if (currentCardiacCycleList.isNotEmpty) {
  45. feature?.refItem.setInnerPoints([
  46. currentCardiacCycleList.first.systoleStart,
  47. currentCardiacCycleList.last.diastoleEnd
  48. ]);
  49. }
  50. feature?.refItem.setCurrentCardiacCycleList([]);
  51. feature!.isActive = false;
  52. doCalculate();
  53. doFeatureFinish();
  54. doFeatureUpdate(); // 若不执行,子测量将无法自动切换
  55. PromptBox.dismiss();
  56. }
  57. @override
  58. void doFeatureFinish() {
  59. super.doFeatureFinish();
  60. }
  61. void synchToMainMonitorScreen(PointInfo args) {
  62. final point = args.toAreaLogicPoint();
  63. var x = point.x;
  64. var y = point.y;
  65. }
  66. void handleMouseDownWhileWaiting(PointInfo args) {
  67. // TODO: 判断是否当前area
  68. // 转换为Area逻辑位置
  69. feature = AutoDopplerTraceFeature(this);
  70. if (args.hostVisualArea != null) {
  71. feature!.hostVisualArea = args.hostVisualArea;
  72. }
  73. final point = args.toAreaLogicPoint();
  74. feature!.adopt(point);
  75. state = ItemStates.running;
  76. }
  77. void handleTouchDownWhileWaiting(PointInfo args) {
  78. print("画结束了");
  79. // TODO: 判断是否当前area
  80. // 转换为Area逻辑位置
  81. feature = AutoDopplerTraceFeature(this);
  82. if (args.hostVisualArea != null) {
  83. feature!.hostVisualArea = args.hostVisualArea;
  84. }
  85. final point = args.toAreaLogicPoint();
  86. feature!.adopt(point);
  87. // state = ItemStates.running;
  88. }
  89. PointInfo? startPoint;
  90. DPoint touchStartPosition = DPoint(0, 0); // 相对位移起始触摸点
  91. bool isFirstPointMove = false;
  92. @override
  93. bool onExecuteTouch(PointInfo args) {
  94. if (state == ItemStates.finished) {
  95. if (args.pointType == PointInfoType.touchDown) {
  96. state = ItemStates.waiting;
  97. }
  98. }
  99. if (state == ItemStates.waiting) {
  100. if (isFirstPointMove) {
  101. args.addOffset(0, -0.2);
  102. }
  103. switch (args.pointType) {
  104. case PointInfoType.touchDown:
  105. isFirstPointMove = false;
  106. startPoint = args; // 设置线段起点
  107. handleTouchDownWhileWaiting(startPoint!); // 通过设置的起点开始一个绘制事件
  108. break;
  109. case PointInfoType.touchUp:
  110. startPoint = args; // 设置线段起点
  111. state = ItemStates.running;
  112. touchState.touchOffset = Offset.zero;
  113. break; // 按下立即抬起无事发生
  114. case PointInfoType.touchMove:
  115. if (isMoveTargetOutOfRange(args)) return true;
  116. isFirstPointMove = true;
  117. final pixelSize = application.displaySize;
  118. touchState.touchOffset =
  119. DPoint(0, -0.2).scale2Size(pixelSize).toOffset();
  120. feature?.innerPoints.first = args;
  121. break;
  122. default:
  123. break;
  124. }
  125. } else if (state == ItemStates.running) {
  126. if (args.pointType == PointInfoType.touchDown) {
  127. touchStartPosition = args;
  128. final pixelSize = application.displaySize;
  129. touchState.touchOffset = startPoint!.scale2Size(pixelSize).toOffset() -
  130. args.scale2Size(pixelSize).toOffset();
  131. }
  132. if (args.pointType == PointInfoType.touchUp) {
  133. touchState.touchOffset = Offset.zero;
  134. doFeatureFinish();
  135. }
  136. if (args.pointType == PointInfoType.touchMove) {
  137. PointInfo newPoint = PointInfo.fromOffset(
  138. startPoint!.clone().addVector(args - touchStartPosition).toOffset(),
  139. startPoint!.pointType);
  140. if (isMoveTargetOutOfRange(newPoint)) return true;
  141. feature?.adopt(newPoint);
  142. doCalculate();
  143. }
  144. }
  145. return true;
  146. }
  147. static AutoDopplerTrace createTrace(
  148. ItemMeta meta, [
  149. IMeasureItem? parent,
  150. ]) {
  151. AutoDopplerTrace trace = AutoDopplerTrace(meta, parent);
  152. trace.calculator = AutoDopplerTraceCal(trace);
  153. return trace;
  154. }
  155. }
  156. class AutoDopplerTraceFeature extends AutoDopplerTraceItemFeatureAbstract {
  157. AutoDopplerTraceFeature(TraceItemAbstract refItem) : super(refItem);
  158. final greenPen = Paint()
  159. ..color = const Color.fromARGB(255, 0, 255, 0)
  160. ..isAntiAlias = true
  161. ..strokeWidth = 1
  162. ..style = PaintingStyle.stroke;
  163. final dashLinePan = Paint()
  164. ..color = const Color.fromARGB(255, 255, 255, 0)
  165. ..isAntiAlias = false
  166. ..strokeWidth = 1
  167. ..style = PaintingStyle.stroke;
  168. DPoint furthestPoint = DPoint(0, 0);
  169. List<CardiacCycle> currentCardiacCycleList = [];
  170. late Path path;
  171. @override
  172. void paint(Canvas canvas, Size size) {
  173. final double areaTop = hostVisualArea!.displayRegion.top * size.height;
  174. final double areaBottom =
  175. hostVisualArea!.displayRegion.bottom * size.height;
  176. if (innerPoints.isEmpty) return;
  177. // if (innerPoints.length == 1) {
  178. // // drawVertex(canvas, convert2ViewPoint(size, innerPoints[0]).toOffset());
  179. // // drawId(canvas, size);
  180. // // return;
  181. // }
  182. final points = innerPoints.map((e) => convert2ViewPoint(size, e)).toList();
  183. final startOffset = convert2ViewPoint(size, startPoint);
  184. final endOffset = convert2ViewPoint(size, endPoint);
  185. double baseLine =
  186. (hostVisualArea!.viewport!.physical as DopplerPhysicalCoordinate)
  187. .baseLine;
  188. double pwHeight = size.height * hostVisualArea!.layoutRegion!.height;
  189. double baseLineHeight = areaTop + baseLine * pwHeight;
  190. // TraceListData data = TraceListData();
  191. List<DPoint> maxPonints = [];
  192. List<DPoint> logicalPoints = [];
  193. /// 当前周期数据
  194. List<CardiacCycle> cardiacCycleList = [];
  195. List<CardiacCycle> logicalCardiacCycleList = [];
  196. List<BestCardiacCycle> logicalBestCardiacCycleList = [];
  197. path = Path();
  198. /// 最大点集
  199. /// below: 1
  200. /// above: 2
  201. if (startOffset.y < baseLineHeight) {
  202. // data.setLocation("above");
  203. logicalPoints = List.generate(
  204. TraceListData.aboveMaxPonints.length,
  205. (index) => TraceListData.aboveMaxPonints[index],
  206. );
  207. logicalCardiacCycleList = List.generate(
  208. TraceListData.aboveCardiacCycleList.length,
  209. (index) => TraceListData.aboveCardiacCycleList[index],
  210. );
  211. logicalBestCardiacCycleList = List.generate(
  212. TraceListData.aboveOptimumCardiacCycleList.length,
  213. (index) => TraceListData.aboveOptimumCardiacCycleList[index],
  214. );
  215. } else {
  216. logicalPoints = List.generate(
  217. TraceListData.belowMaxPonints.length,
  218. (index) => TraceListData.belowMaxPonints[index],
  219. );
  220. logicalCardiacCycleList = List.generate(
  221. TraceListData.belowCardiacCycleList.length,
  222. (index) => TraceListData.belowCardiacCycleList[index],
  223. );
  224. logicalBestCardiacCycleList = List.generate(
  225. TraceListData.belowOptimumCardiacCycleList.length,
  226. (index) => TraceListData.belowOptimumCardiacCycleList[index],
  227. );
  228. }
  229. maxPonints = logicalPoints.map((e) => convert2ViewPoint(size, e)).toList();
  230. for (int i = 0; i < logicalCardiacCycleList.length; i++) {
  231. logicalCardiacCycleList[i].index = i;
  232. cardiacCycleList.add(logicalCardiacCycleList[i]);
  233. }
  234. /// 这是正在绘制的
  235. if (points.isNotEmpty) {
  236. /// 这边自动测量的最佳周期先默认选择3 后面需要配置
  237. if (logicalBestCardiacCycleList.length > 3) {
  238. if (logicalBestCardiacCycleList[2] == null) {
  239. return;
  240. }
  241. DPoint logicalBestCardiacCycleStartDPoint = convert2ViewPoint(
  242. size, logicalBestCardiacCycleList[2].bestSystoleStart);
  243. DPoint logicalBestCardiacCycleEndDPoint = convert2ViewPoint(
  244. size, logicalBestCardiacCycleList[2].bestDiastoleEnd);
  245. // 绘制路径
  246. final path = Path()
  247. ..moveTo(logicalBestCardiacCycleStartDPoint.x,
  248. logicalBestCardiacCycleStartDPoint.y);
  249. for (var point in maxPonints) {
  250. if (point.x >= logicalBestCardiacCycleStartDPoint.x &&
  251. point.x <= logicalBestCardiacCycleEndDPoint.x) {
  252. path.lineTo(point.x, point.y);
  253. }
  254. }
  255. canvas.drawPath(path, greenPen);
  256. /// 根据周期绘制
  257. for (CardiacCycle i in cardiacCycleList) {
  258. DPoint diastoleEnd = convert2ViewPoint(size, i.diastoleEnd);
  259. DPoint systoleStart = convert2ViewPoint(size, i.systoleStart);
  260. DPoint ePeak = convert2ViewPoint(size, i.ePeak ?? DPoint(0, 0));
  261. /// ai提供的周期数据可能不对
  262. if (systoleStart.x <= diastoleEnd.x &&
  263. systoleStart.x >= logicalBestCardiacCycleStartDPoint.x &&
  264. diastoleEnd.x <= logicalBestCardiacCycleEndDPoint.x) {
  265. canvas.drawDashLine(
  266. Offset(systoleStart.x, areaTop),
  267. Offset(systoleStart.x, areaBottom),
  268. 3,
  269. 3,
  270. dashLinePan,
  271. );
  272. canvas.drawDashLine(
  273. Offset(diastoleEnd.x, areaTop),
  274. Offset(diastoleEnd.x, areaBottom),
  275. 3,
  276. 3,
  277. dashLinePan,
  278. );
  279. if (ePeak.x < endOffset.x) {
  280. drawCrossVertex(
  281. canvas,
  282. ePeak.toOffset(),
  283. );
  284. }
  285. currentCardiacCycleList.add(i);
  286. }
  287. }
  288. }
  289. refItem
  290. .setCurrentCardiacCycleList(currentCardiacCycleList.toSet().toList());
  291. canvas.drawPath(
  292. path,
  293. greenPen,
  294. );
  295. }
  296. }
  297. /// 转成物理坐标
  298. DPoint convert2LogicPoint(
  299. Size size,
  300. DPoint viewPoint,
  301. double baseLine,
  302. double width,
  303. double height,
  304. ) {
  305. final x = viewPoint.x / size.width * width;
  306. final y = viewPoint.y - (baseLine * size.height) * height;
  307. return DPoint(x, y);
  308. }
  309. }
  310. abstract class AutoDopplerTraceItemFeatureAbstract extends MeasureItemFeature {
  311. AutoDopplerTraceItemFeatureAbstract(TraceItemAbstract refItem)
  312. : super(refItem);
  313. @override
  314. TraceItemAbstract get refItem => super.refItem as TraceItemAbstract;
  315. DPoint get startPoint => innerPoints.first;
  316. DPoint get endPoint => innerPoints.last;
  317. bool ifRightSide = true;
  318. /// 接收新坐标
  319. void adopt(DPoint point) {
  320. if (innerPoints.isEmpty) {
  321. innerPoints.add(point);
  322. }
  323. if (point.x > startPoint.x) {
  324. handleChangeSide(point);
  325. if (point.x < innerPoints.last.x) {
  326. clearRight(point.x);
  327. } else if (point.x > innerPoints.last.x) {
  328. innerPoints.add(point);
  329. }
  330. } else {
  331. handleChangeSide(point);
  332. if (point.x > innerPoints.last.x) {
  333. clearLeft(point.x);
  334. } else if (point.x < innerPoints.last.x) {
  335. innerPoints.add(point);
  336. }
  337. }
  338. }
  339. void clearRight(double X) {
  340. if (innerPoints.isEmpty) return;
  341. for (var i = innerPoints.length - 1; i >= 0; i--) {
  342. if (innerPoints[i].x >= X) {
  343. innerPoints.removeAt(i);
  344. }
  345. }
  346. }
  347. void clearLeft(double X) {
  348. if (innerPoints.isEmpty) return;
  349. for (var i = innerPoints.length - 1; i >= 0; i--) {
  350. if (innerPoints[i].x <= X) {
  351. innerPoints.removeAt(i);
  352. }
  353. }
  354. }
  355. void handleChangeSide(point) {
  356. if (ifRightSide) {
  357. if (point.x < startPoint.x) {
  358. ifRightSide = false;
  359. innerPoints.clear();
  360. innerPoints.add(point);
  361. }
  362. } else {
  363. if (point.x > startPoint.x) {
  364. ifRightSide = true;
  365. innerPoints.clear();
  366. innerPoints.add(point);
  367. }
  368. }
  369. }
  370. }
  371. abstract class TraceItemAbstract
  372. extends MeasureItem<AutoDopplerTraceItemFeatureAbstract> {
  373. TraceItemAbstract(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);
  374. List<CardiacCycle> currentCardiacCycleList = [];
  375. void setCurrentCardiacCycleList(List<CardiacCycle> _currentCardiacCycleList) {
  376. currentCardiacCycleList = _currentCardiacCycleList;
  377. }
  378. void setInnerPoints(List<DPoint> points) {
  379. feature!.innerPoints.clear();
  380. feature!.innerPoints.addAll(points);
  381. }
  382. void clearInnerPoints() {
  383. feature!.innerPoints.clear();
  384. }
  385. }