semiauto_trace.dart 16 KB

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