semiauto_trace.dart 14 KB

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