measure_page_test.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. import 'dart:typed_data';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:fis_measure/interfaces/enums/annotation.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/items/terms.dart';
  7. import 'package:fis_measure/interfaces/process/items/types.dart';
  8. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  9. import 'package:fis_measure/interfaces/process/standard_line/calibration.dart';
  10. import 'package:fis_measure/interfaces/process/visuals/visual_area.dart';
  11. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  12. import 'package:fis_measure/interfaces/process/workspace/exam_info.dart';
  13. import 'package:fis_measure/interfaces/process/workspace/measure_controller.dart';
  14. import 'package:fis_measure/process/items/item_meta_convert.dart';
  15. import 'package:fis_measure/process/workspace/measure_3d_view_controller.dart';
  16. import 'package:fis_measure/process/workspace/measure_controller.dart';
  17. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  18. import 'package:fis_measure/view/measure/measure_main_view.dart';
  19. import 'package:flutter/material.dart';
  20. import 'package:get/get.dart';
  21. import 'package:vid/us/vid_us_mode.dart';
  22. import 'item_create_test.dart';
  23. import 'process/workspace/measure_handler.dart';
  24. class MeasureDataTester {
  25. static Future<List<RemedicalItemList>> getRemedicalList(
  26. String patientCode,
  27. String recordCode,
  28. String token,
  29. ) async {
  30. return [];
  31. }
  32. static Future<RemedicalInfoDTO?> getImageInfo(
  33. String remedicalCode,
  34. String token,
  35. ) async {
  36. return null;
  37. }
  38. static Future<MeasureApplicationDTO?> getMeasureApplication(
  39. dynamic args) async {
  40. return null;
  41. }
  42. static Future<MeasureApplicationDTO?> saveUserDefinedMeasureApplicationAsync(
  43. dynamic args) async {
  44. return null;
  45. }
  46. static Future<bool?> saveImage(
  47. Uint8List imageBytes,
  48. String patientCode,
  49. String recordCode,
  50. String remedicalCode,
  51. String measuredData,
  52. ) async {
  53. return null;
  54. }
  55. static Future<void> saveMeasureSystemSettingAsync(
  56. MeasureSystemSettingDTO measureSystemSetting) async {
  57. return;
  58. }
  59. static Future<MeasureSystemSettingDTO?> getMeasureSystemSettingAsync() async {
  60. return null;
  61. }
  62. static Future<String> shareImage(String vid) async {
  63. return '';
  64. }
  65. static Future<CommentItemResultDTO?> getCommentsByApplicationAsync(
  66. String applicationName, String categoryName) async {
  67. return null;
  68. }
  69. static Future<bool?> saveUserDefinedCommentsAsync(String applicationName,
  70. String categoryName, List<CommentItemDTO> commentItems) async {
  71. return null;
  72. }
  73. }
  74. class MeasureTestPage extends StatefulWidget {
  75. const MeasureTestPage({Key? key}) : super(key: key);
  76. // ignore: non_constant_identifier_names
  77. static List<dynamic> MetaDTOList = [];
  78. @override
  79. State<StatefulWidget> createState() => _MeasureTestPageState();
  80. }
  81. class _MeasureTestPageState extends State<MeasureTestPage> {
  82. static const C_LINEAR_TISSUE =
  83. // "http://cdn-bj.fis.plus/9F066341FA874E21B48CDE247C13D495.vid"; //B TVI TD
  84. // "http://cdn-bj.fis.plus/974BABA5113640639FD749E06DD7DA5B.vid"; //B CF CW
  85. "http://cdn-bj.fis.plus/0B344F48BA574ECD82B7FEDB8848421A.vid"; //单幅TM
  86. // "http://cdn-bj.fis.plus/3379F38302884C2991D90FBDFB0DEA7E.dat"; //单幅TM(2)
  87. // "http://cdn-bj.fis.plus/6A99AD2530864616B64355A8EA9AE3EC.vid";
  88. // "http://cdn-bj.fis.plus/F26C6E5D57A7472A97E9EB543DF0D16C.vid"; // 单幅Convex
  89. // "http://cdn-bj.fis.plus/6B6E069659D14E7299EB9F6EFCDE9C8C.vid"; //双幅单TissueConvex
  90. // "http://cdn-bj.fis.plus/062643B82365437DB95F3811580AF3ED.vid"; //四幅单模式
  91. // "http://cdn-bj.fis.plus/EA90D146049D416E8E466B7446E00001.vid"; //四幅Doppler
  92. // "http://cdn-bj.fis.plus/3rd_linearTvTissue2.vid"; //魔盒
  93. // "http://cdn-bj.fis.plus/81FFF8E5E078473FA687FBE81C4869B1.vid"; // 魔盒TV
  94. // "http://cdn-bj.fis.plus/7B450708A2784B1490304C82787349BE.vid";// 胎儿Zoom
  95. static const C_CONVEX_TISSUE =
  96. "http://cdn-bj.fis.plus/FEB1AAE5D9C24839BEE31B16E8CB450A.vid";
  97. final _3dc = Get.put<Measure3DViewController>(Measure3DViewController());
  98. final datac = Get.put<MeasureDataController>(MeasureDataController(
  99. MeasureDataTester.getRemedicalList,
  100. MeasureDataTester.getImageInfo,
  101. MeasureDataTester.getMeasureApplication,
  102. MeasureDataTester.saveUserDefinedMeasureApplicationAsync,
  103. MeasureDataTester.saveImage,
  104. MeasureDataTester.saveMeasureSystemSettingAsync,
  105. MeasureDataTester.getMeasureSystemSettingAsync,
  106. MeasureDataTester.shareImage,
  107. MeasureDataTester.getCommentsByApplicationAsync,
  108. MeasureDataTester.saveUserDefinedCommentsAsync,
  109. ));
  110. final measureHandler = Get.put(MeasureHandler());
  111. final controller = Get.put<IMeasureController>(MeasureController(
  112. "12345",
  113. imagesFetchFunc: (code) async {
  114. return <ExamImageInfo>[
  115. ExamImageInfo(C_LINEAR_TISSUE, C_LINEAR_TISSUE),
  116. ExamImageInfo(C_CONVEX_TISSUE, C_CONVEX_TISSUE)
  117. ];
  118. },
  119. ));
  120. bool loaded = false;
  121. int opType = 0;
  122. bool useArrowAnnotation = false;
  123. @override
  124. void initState() {
  125. measureHandler.changeImageLoaded;
  126. controller.load().then((value) {
  127. // 加载指定图像
  128. controller.examInfo.selectedImageIndex = 0;
  129. });
  130. controller.imageLoaded.addListener(onImageLoaded);
  131. super.initState();
  132. }
  133. @override
  134. void dispose() {
  135. controller.imageLoaded.removeListener(onImageLoaded);
  136. controller.dispose();
  137. Get.delete<IMeasureController>();
  138. super.dispose();
  139. }
  140. void onImageLoaded(Object sender, ExamImageInfo? e) {
  141. if (!mounted) return;
  142. if (e != null) {
  143. Future.delayed(const Duration(milliseconds: 100), () {
  144. controller.playerController.play();
  145. });
  146. setState(() {
  147. loaded = true;
  148. });
  149. }
  150. }
  151. @override
  152. Widget build(BuildContext context) {
  153. Widget body;
  154. if (!loaded) {
  155. const loadingWidget = Center(child: CircularProgressIndicator());
  156. body = Row(
  157. children: const [
  158. SizedBox(
  159. width: 300,
  160. child: loadingWidget,
  161. ),
  162. VerticalDivider(),
  163. Expanded(child: loadingWidget),
  164. ],
  165. );
  166. } else {
  167. body = Row(
  168. key: ValueKey(controller.examInfo.selectedImageIndex),
  169. children: [
  170. opType == 1
  171. ? const _MeasureLeftAnnotation()
  172. : const _MeasureLeftBoard(),
  173. const VerticalDivider(),
  174. const Expanded(
  175. child: MeasureRightBoard(),
  176. ),
  177. ],
  178. );
  179. }
  180. return Scaffold(
  181. backgroundColor: const Color.fromARGB(255, 53, 55, 51),
  182. appBar: AppBar(
  183. actions: [
  184. TextButton(
  185. onPressed: () {
  186. Get.find<IApplication>().clearRecords();
  187. },
  188. child: const Text(
  189. "清空",
  190. style: TextStyle(
  191. color: Colors.white,
  192. ),
  193. ),
  194. ),
  195. TextButton(
  196. onPressed: () {
  197. Get.find<IApplication>().undoRecord();
  198. },
  199. child: const Text(
  200. "撤销",
  201. style: TextStyle(
  202. color: Colors.white,
  203. ),
  204. ),
  205. ),
  206. TextButton(
  207. onPressed: () {
  208. final c = Get.find<IStandardLineCalibrationController>();
  209. if (c.isEditing) {
  210. c.cancelEdit();
  211. } else {
  212. c.enterEditMode();
  213. }
  214. },
  215. child: const Text(
  216. "校准线",
  217. style: TextStyle(
  218. color: Colors.white,
  219. ),
  220. ),
  221. ),
  222. TextButton.icon(
  223. onPressed: () {
  224. setState(() {
  225. if (useArrowAnnotation) {
  226. useArrowAnnotation = false;
  227. controller.workingApplication.switchAnnotation();
  228. } else {
  229. useArrowAnnotation = true;
  230. controller.workingApplication
  231. .switchAnnotation(AnnotationType.arrow);
  232. }
  233. });
  234. },
  235. icon: const Icon(Icons.arrow_right_alt_sharp),
  236. label: Text(
  237. "箭头",
  238. style: TextStyle(
  239. color: useArrowAnnotation ? Colors.amber : Colors.white,
  240. ),
  241. ),
  242. ),
  243. TextButton(
  244. onPressed: () {
  245. setState(() {
  246. opType = opType == 1 ? 0 : 1;
  247. });
  248. },
  249. child: Text(
  250. opType == 1 ? "注释" : "测量",
  251. style: const TextStyle(
  252. color: Colors.white,
  253. ),
  254. ),
  255. ),
  256. TextButton(
  257. onPressed: () {
  258. if (controller.examInfo.selectedImageIndex == 0) return;
  259. controller.examInfo.selectedImageIndex = 0;
  260. },
  261. child: Text(
  262. '线阵',
  263. style: TextStyle(
  264. color: controller.examInfo.selectedImageIndex == 0
  265. ? Colors.amber
  266. : Colors.white,
  267. ),
  268. ),
  269. ),
  270. TextButton(
  271. onPressed: () {
  272. if (controller.examInfo.selectedImageIndex == 1) return;
  273. controller.examInfo.selectedImageIndex = 1;
  274. },
  275. child: Text(
  276. '扇阵',
  277. style: TextStyle(
  278. color: controller.examInfo.selectedImageIndex == 1
  279. ? Colors.amber
  280. : Colors.white,
  281. ),
  282. ),
  283. ),
  284. ],
  285. leading: IconButton(
  286. onPressed: () {
  287. Navigator.of(context).pop();
  288. },
  289. icon: const Icon(Icons.arrow_back),
  290. ),
  291. ),
  292. body: body,
  293. floatingActionButton: _ModeTips(),
  294. floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
  295. );
  296. }
  297. }
  298. class MeasureRightBoard extends StatefulWidget {
  299. const MeasureRightBoard({Key? key}) : super(key: key);
  300. @override
  301. State<StatefulWidget> createState() => _MeasureRightBoardState();
  302. }
  303. class _MeasureRightBoardState extends State<MeasureRightBoard> {
  304. final playerController = Get.find<IPlayerController>();
  305. @override
  306. Widget build(BuildContext context) {
  307. return Container(
  308. padding: const EdgeInsets.all(8).copyWith(left: 0),
  309. child: Stack(
  310. children: const [
  311. MeasureMainView(),
  312. _PlayerTips(),
  313. ],
  314. ),
  315. );
  316. }
  317. }
  318. class _PlayerTips extends StatefulWidget {
  319. const _PlayerTips({Key? key}) : super(key: key);
  320. @override
  321. State<StatefulWidget> createState() => _PlayerTipsState();
  322. }
  323. class _PlayerTipsState extends State<_PlayerTips> {
  324. final playerController = Get.find<IPlayerController>();
  325. bool loading = false;
  326. String content = 'xxxxxxxxxxxxxxxxxxxx';
  327. @override
  328. void initState() {
  329. playerController.frameLoadStateChanged.addListener(_onLoadStateChanged);
  330. super.initState();
  331. }
  332. @override
  333. void dispose() {
  334. playerController.frameLoadStateChanged.removeListener(_onLoadStateChanged);
  335. super.dispose();
  336. }
  337. void _onLoadStateChanged(sender, bool e) {
  338. loading = e;
  339. if (loading) {
  340. Future.delayed(
  341. const Duration(milliseconds: 100),
  342. () {
  343. setState(() {
  344. content = loading ? "Loading。。。" : "";
  345. });
  346. },
  347. );
  348. } else {
  349. setState(() {
  350. content = "";
  351. });
  352. }
  353. }
  354. @override
  355. Widget build(BuildContext context) {
  356. return Positioned(
  357. child: Center(
  358. child: Text(
  359. content,
  360. style: const TextStyle(
  361. color: Colors.white,
  362. fontSize: 28,
  363. ),
  364. ),
  365. ),
  366. top: Get.size.height * 0.44,
  367. left: 0,
  368. right: 0,
  369. );
  370. }
  371. }
  372. class _MeasureLeftBoard extends StatefulWidget {
  373. const _MeasureLeftBoard({Key? key}) : super(key: key);
  374. @override
  375. State<StatefulWidget> createState() => _MeasureLeftBoardState();
  376. }
  377. class _MeasureLeftBoardState extends State<_MeasureLeftBoard> {
  378. // ignore: non_constant_identifier_names
  379. static final C_SUPPORTED_ITEMS = <String>[
  380. MeasureTerms.Distance,
  381. MeasureTerms.Perimeter,
  382. MeasureTerms.Area,
  383. MeasureTerms.Angle,
  384. MeasureTerms.Depth,
  385. MeasureTerms.Volume,
  386. MeasureTerms.Stenosis,
  387. MeasureTerms.AbRatio,
  388. MeasureTerms.RUV,
  389. //
  390. MeasureTypes.AreaPerimeterEllipse,
  391. MeasureTypes.AreaPerimeterPolyline,
  392. MeasureTypes.AreaPerimeterSpline,
  393. ];
  394. // ignore: non_constant_identifier_names
  395. static final C_SUPPORTED_M_ITEMS = <String>[
  396. MeasureTerms.VerticalDistance,
  397. MeasureTerms.Timespan,
  398. MeasureTerms.Depth,
  399. MeasureTerms.Stenosis,
  400. MeasureTerms.AbRatio,
  401. MeasureTerms.Slope,
  402. MeasureTerms.TAMAX,
  403. MeasureTerms.Velocity,
  404. MeasureTerms.Acceleration,
  405. MeasureTerms.PSED,
  406. MeasureTerms.RI,
  407. MeasureTerms.MaxPG,
  408. "LV TEI Index",
  409. "AV Ratio",
  410. MeasureTerms.HeartRate,
  411. // MeasureTerms.PHT,
  412. "MV PHT",
  413. "Qp/Qs",
  414. ];
  415. late final List<String> passeItems;
  416. final scrollController = ScrollController();
  417. final application = Get.find<IApplication>();
  418. String modeType = 'Tissue';
  419. int activeIndex = 0;
  420. List<ItemMeta> workingItems = [];
  421. @override
  422. void initState() {
  423. // passeItems = C_SUPPORTED_ITEMS;
  424. // passeItems = TestItems.C_DISTANCE_ITEMS;
  425. passeItems = TestItems.C_TEST_ITEMS;
  426. loadItems();
  427. application.visualAreaChanged.addListener(_visualAreaChanged);
  428. super.initState();
  429. }
  430. @override
  431. dispose() {
  432. application.visualAreaChanged.removeListener(_visualAreaChanged);
  433. super.dispose();
  434. }
  435. void loadItems() {
  436. workingItems = [];
  437. var names = (modeType == "TissueTM" || modeType == "Doppler")
  438. ? C_SUPPORTED_M_ITEMS
  439. : passeItems;
  440. final workingItemDtos =
  441. MeasureTestPage.MetaDTOList.where((e) => names.contains(e['Name']));
  442. for (var map in workingItemDtos) {
  443. final dto = ItemMetaDTO.fromJson(map);
  444. final item = ItemMetaConverter(dto).output();
  445. workingItems.add(item);
  446. }
  447. }
  448. void _visualAreaChanged(sender, IVisualArea e) {
  449. if (mounted) {
  450. _setAvailableModes(e.mode.modeType.toString().split('.')[1]);
  451. // changeItemByMeta(0); //暂时不要自动切测量项
  452. setState(() {});
  453. }
  454. }
  455. void _setAvailableModes(String name) {
  456. modeType = name;
  457. loadItems();
  458. }
  459. @override
  460. Widget build(BuildContext context) {
  461. return Container(
  462. width: 300,
  463. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  464. child: Scrollbar(
  465. controller: scrollController,
  466. isAlwaysShown: true,
  467. child: ListView.separated(
  468. controller: scrollController,
  469. itemCount: workingItems.length,
  470. itemBuilder: (BuildContext context, int index) {
  471. final meta = workingItems[index];
  472. final active = index == activeIndex;
  473. return active
  474. ? ElevatedButton(
  475. onPressed: () {
  476. changeItemByMeta(index);
  477. },
  478. child: Text(meta.name),
  479. style: ElevatedButton.styleFrom(
  480. fixedSize: const Size.fromHeight(50),
  481. ),
  482. )
  483. : OutlinedButton(
  484. onPressed: () => changeItemByMeta(index),
  485. child: Text(meta.name),
  486. style: OutlinedButton.styleFrom(
  487. fixedSize: const Size.fromHeight(50),
  488. ),
  489. );
  490. },
  491. separatorBuilder: (BuildContext context, int index) {
  492. return const SizedBox(height: 8);
  493. },
  494. ),
  495. ),
  496. );
  497. }
  498. void changeItemByMeta(int index) {
  499. setState(() {
  500. activeIndex = index;
  501. });
  502. final meta = workingItems[index];
  503. application.switchItem(meta);
  504. print(application.activeMeasureItem?.meta.name);
  505. // handle combo item
  506. if (application.activeMeasureItem != null) {
  507. final item = application.activeMeasureItem!;
  508. if (item is ITopMeasureItem) {
  509. item.switchChild(0);
  510. }
  511. }
  512. }
  513. }
  514. class _MeasureLeftAnnotation extends StatefulWidget {
  515. const _MeasureLeftAnnotation({Key? key}) : super(key: key);
  516. @override
  517. State<StatefulWidget> createState() => _MeasureLeftAnnotationState();
  518. }
  519. class _MeasureLeftAnnotationState extends State<_MeasureLeftAnnotation> {
  520. // ignore: non_constant_identifier_names
  521. static final C_SUPPORTED_TEXTS = <String>[
  522. "肝左叶",
  523. "胆囊",
  524. "脾脏",
  525. "结石",
  526. "积液",
  527. ];
  528. final scrollController = ScrollController();
  529. final application = Get.find<IApplication>();
  530. @override
  531. void initState() {
  532. // application.switchAnnotation(AnnotationType.label, C_SUPPORTED_TEXTS[0]);
  533. // application.switchAnnotation(AnnotationType.arrow);
  534. application.switchAnnotation(AnnotationType.input);
  535. super.initState();
  536. }
  537. @override
  538. Widget build(BuildContext context) {
  539. return Container(
  540. width: 300,
  541. padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
  542. child: Scrollbar(
  543. controller: scrollController,
  544. isAlwaysShown: true,
  545. child: ListView.separated(
  546. controller: scrollController,
  547. itemCount: C_SUPPORTED_TEXTS.length,
  548. itemBuilder: (BuildContext context, int index) {
  549. final name = C_SUPPORTED_TEXTS[index];
  550. const style = TextStyle(color: Colors.white, fontSize: 16);
  551. const dragStyle = TextStyle(color: Colors.amber, fontSize: 18);
  552. // TODO: melon - set drag cursor after version updated up then 3.0
  553. // https://github.com/flutter/flutter/pull/100475
  554. return Draggable<String>(
  555. data: name,
  556. dragAnchorStrategy: (data, context, offset) {
  557. // return offset - Offset(120, 14);
  558. return Offset.zero;
  559. },
  560. child: OutlinedButton(
  561. child: Text(name, style: style),
  562. onPressed: () {
  563. application.switchAnnotation(AnnotationType.label, name);
  564. },
  565. style: OutlinedButton.styleFrom(
  566. shape: RoundedRectangleBorder(
  567. borderRadius: BorderRadius.circular(4),
  568. ),
  569. side: BorderSide(color: Colors.grey.shade100),
  570. fixedSize: const Size.fromHeight(44),
  571. ),
  572. ),
  573. feedback: Material(
  574. color: Colors.transparent,
  575. child: Text(name, style: dragStyle),
  576. ),
  577. onDragStarted: () {
  578. application.switchAnnotation(AnnotationType.label, name);
  579. },
  580. );
  581. },
  582. separatorBuilder: (BuildContext context, int index) {
  583. return const SizedBox(height: 8);
  584. },
  585. ),
  586. ),
  587. );
  588. }
  589. }
  590. class _ModeTips extends StatefulWidget {
  591. @override
  592. State<StatefulWidget> createState() => _ModeTipsState();
  593. }
  594. class _ModeTipsState extends State<_ModeTips> {
  595. IApplication? application;
  596. @override
  597. void initState() {
  598. Future.delayed(const Duration(milliseconds: 100), () {
  599. application = Get.find<IApplication>();
  600. application!.visualsLoaded.addListener(_onVisualsLoaded);
  601. });
  602. super.initState();
  603. }
  604. @override
  605. void dispose() {
  606. application!.visualsLoaded.removeListener(_onVisualsLoaded);
  607. super.dispose();
  608. }
  609. void _onVisualsLoaded(Object sender, void e) {
  610. setState(() {});
  611. }
  612. @override
  613. Widget build(BuildContext context) {
  614. if (application == null) return const Text("Wait");
  615. return Material(
  616. child: Text(application!.avaliableModes.map((e) => e.name).join('/')),
  617. );
  618. }
  619. }