measure_page_test.dart 20 KB

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