measure_page_test.dart 18 KB

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