ai_resul_info.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. import 'dart:convert';
  2. import 'dart:math';
  3. import 'package:fis_common/logger/logger.dart';
  4. import 'package:fis_i18n/i18n.dart';
  5. import 'package:fis_measure/interfaces/process/standard_line/calibration.dart';
  6. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  7. import 'package:fis_measure/process/visual/visual.dart';
  8. import 'package:fis_measure/process/workspace/third_part/application.dart';
  9. import 'package:fis_measure/process/workspace/third_part/calibration_controller.dart';
  10. import 'package:fis_measure/process/workspace/visual_loader.dart';
  11. import 'package:fis_measure/values/unit_desc.dart';
  12. import 'package:fis_measure/view/paint/ai_patint_controller.dart';
  13. import 'package:fis_measure/view/paint/date_structure.dart';
  14. import 'package:flutter/material.dart';
  15. import 'package:get/get.dart';
  16. import 'package:vid/us/vid_us_unit.dart';
  17. /// AI诊断结果
  18. class ResultInfo extends StatefulWidget {
  19. const ResultInfo(this.aiDetectedObject, {Key? key}) : super(key: key);
  20. final List<AIDetectedObject>? aiDetectedObject;
  21. @override
  22. State<ResultInfo> createState() => _ResultInfoState();
  23. }
  24. class _ResultInfoState extends State<ResultInfo> {
  25. late final aiPatintController = Get.find<AiPatintController>();
  26. IStandardLineCalibrationController? standardLineCalibrationController;
  27. late AIDetectedObject aiDetectedObjectItem;
  28. double _unitsPhysicalPixels = 0;
  29. late final application = Get.find<IApplication>();
  30. // 图像的物理单位
  31. String _xUnit = '';
  32. @override
  33. void initState() {
  34. _updateImagePhysicalSize();
  35. try {
  36. if (application.isThirdPart) {
  37. final standradLine = (application as ThirdPartApplication).standardLine;
  38. standardLineCalibrationController =
  39. StandardLineCalibrationController(application, standradLine);
  40. Get.put<IStandardLineCalibrationController>(
  41. standardLineCalibrationController!);
  42. if (standardLineCalibrationController != null) {
  43. standardLineCalibrationController!.editStateChanged
  44. .addListener(onStandardLineUpdated);
  45. }
  46. }
  47. } catch (e) {
  48. logger.e('standardLineCalibrationController cannot find ', e);
  49. }
  50. super.initState();
  51. }
  52. @override
  53. void didUpdateWidget(ResultInfo oldWidget) {
  54. _updateImagePhysicalSize();
  55. super.didUpdateWidget(oldWidget);
  56. }
  57. @override
  58. void dispose() {
  59. if (standardLineCalibrationController != null) {
  60. standardLineCalibrationController!.editStateChanged
  61. .removeListener(onStandardLineUpdated);
  62. }
  63. super.dispose();
  64. }
  65. void onStandardLineUpdated(_, e) {
  66. _updateImagePhysicalSize();
  67. }
  68. @override
  69. Widget build(BuildContext context) {
  70. if (_unitsPhysicalPixels <= 0) {
  71. _updateImagePhysicalSize();
  72. }
  73. final description = widget
  74. .aiDetectedObject?[aiPatintController.state.aiResultIndex].descriptions;
  75. var lesionSizeDescription = description?.firstWhereOrNull(
  76. (element) => element.type == DiagnosisDescriptionEnum.LesionSize);
  77. var lesionSize = lesionSizeDescription?.value ?? '';
  78. late final lesionSizeMap =
  79. (description?.length ?? 0) > 1 ? jsonDecode(lesionSize) : '';
  80. return Container(
  81. decoration: BoxDecoration(
  82. border: Border.all(
  83. color: Colors.grey,
  84. ),
  85. borderRadius: BorderRadius.circular(4),
  86. color: Colors.transparent,
  87. ),
  88. padding: const EdgeInsets.only(bottom: 10),
  89. child: Column(
  90. children: [
  91. Row(
  92. mainAxisSize: MainAxisSize.max,
  93. children: [
  94. Expanded(
  95. child: Container(
  96. decoration: const BoxDecoration(
  97. borderRadius: BorderRadius.only(
  98. topLeft: Radius.circular(4),
  99. topRight: Radius.circular(4),
  100. ),
  101. color: Color.fromRGBO(54, 169, 206, 1),
  102. ),
  103. padding: const EdgeInsets.only(
  104. top: 4,
  105. bottom: 10,
  106. left: 8,
  107. right: 8,
  108. ),
  109. child: Text(
  110. i18nBook.measure.aiDiagnosticResults.t,
  111. style: const TextStyle(
  112. color: Colors.white,
  113. ),
  114. ),
  115. ),
  116. )
  117. ],
  118. ),
  119. Row(
  120. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  121. children: [
  122. Expanded(
  123. child: Container(
  124. padding: const EdgeInsets.only(
  125. left: 10,
  126. ),
  127. child: Column(
  128. crossAxisAlignment: CrossAxisAlignment.start,
  129. mainAxisAlignment: MainAxisAlignment.start,
  130. children: [
  131. _buildTitle(
  132. i18nBook.measure.diseaseLabels.t,
  133. _buildAITitle(),
  134. ),
  135. if (lesionSizeMap != '' && lesionSizeMap != null)
  136. _buildTitle(
  137. i18nBook.measure.isLesionSize.t,
  138. _buildLesionSize(
  139. lesionSizeMap?['HorizontalLengthInPixel'] ?? 0,
  140. lesionSizeMap?['VerticalLengthInPixel'] ?? 0,
  141. _unitsPhysicalPixels,
  142. ),
  143. )
  144. ],
  145. ),
  146. ),
  147. ),
  148. Container(
  149. margin: const EdgeInsets.symmetric(
  150. vertical: 5,
  151. ),
  152. padding: const EdgeInsets.only(right: 8),
  153. child: Column(
  154. children: [
  155. Text(
  156. i18nBook.measure.possibility.t,
  157. style: const TextStyle(
  158. color: Colors.grey,
  159. ),
  160. ),
  161. const SizedBox(
  162. height: 8,
  163. ),
  164. SizedBox(
  165. width: 70,
  166. height: 70,
  167. child: Stack(
  168. children: [
  169. SizedBox(
  170. width: 70,
  171. height: 70,
  172. child: Obx(() {
  173. final aiDetected = widget.aiDetectedObject?[
  174. aiPatintController.state.aiResultIndex];
  175. return CircularProgressIndicator(
  176. valueColor: AlwaysStoppedAnimation(
  177. _buildAITextColor(
  178. aiDetected?.label ?? 0,
  179. ),
  180. ),
  181. backgroundColor: Colors.grey,
  182. value: aiDetected?.confidence,
  183. );
  184. }),
  185. ),
  186. Center(
  187. child: SizedBox(
  188. width: 65,
  189. height: 45,
  190. child: Obx(
  191. () {
  192. final confidence = widget
  193. .aiDetectedObject?[aiPatintController
  194. .state.aiResultIndex]
  195. .confidence;
  196. return Center(
  197. child: Text(
  198. '${((confidence ?? 0) * 100).toStringAsFixed(1)}%',
  199. style: const TextStyle(
  200. color: Colors.white,
  201. fontSize: 18,
  202. fontWeight: FontWeight.bold,
  203. ),
  204. ),
  205. );
  206. },
  207. ),
  208. ),
  209. ),
  210. ],
  211. ),
  212. ),
  213. ],
  214. ),
  215. ),
  216. ],
  217. ),
  218. ],
  219. ),
  220. );
  221. }
  222. Widget _buildLesionSize(
  223. int horizontalLengthInPixel,
  224. int verticalLengthInPixel,
  225. double unitsPhysicalPixels,
  226. ) {
  227. return Text(
  228. (horizontalLengthInPixel * unitsPhysicalPixels)
  229. .toStringAsFixed(2)
  230. .toString() +
  231. '$_xUnit x' +
  232. (verticalLengthInPixel * unitsPhysicalPixels)
  233. .toStringAsFixed(2)
  234. .toString() +
  235. _xUnit,
  236. style: const TextStyle(color: Colors.white),
  237. );
  238. }
  239. /// 更新图像物理尺度信息
  240. void _updateImagePhysicalSize() {
  241. if (application.visuals.isEmpty) {
  242. return;
  243. }
  244. if (application.visuals[0].visualAreas.isEmpty) {
  245. return;
  246. }
  247. _unitsPhysicalPixels =
  248. (application.visuals[0].visualAreas[0].viewport?.region.width)! /
  249. (application.frameData!.width).toDouble();
  250. VidUsUnit targetUnit =
  251. application.visuals[0].visualAreas[0].viewport?.xUnit ?? VidUsUnit.cm;
  252. _xUnit = UnitDescriptionMap.getDesc(targetUnit);
  253. }
  254. Widget _buildTitle(String label, Widget value) {
  255. return Column(
  256. mainAxisSize: MainAxisSize.max,
  257. crossAxisAlignment: CrossAxisAlignment.start,
  258. children: [
  259. Text(
  260. label,
  261. style: const TextStyle(
  262. color: Color.fromRGBO(54, 169, 206, 1),
  263. ),
  264. ),
  265. value,
  266. const SizedBox(
  267. height: 5,
  268. ),
  269. ],
  270. );
  271. }
  272. Color _buildAITextColor(int label) {
  273. switch (aiPatintController.diagnosisOrgan) {
  274. /// 乳腺是0:未见异常; 1、2、3良性; 4、5、6、7恶性;
  275. case DiagnosisOrganEnum.Breast:
  276. switch (label) {
  277. case 0:
  278. return Colors.lightBlue;
  279. case 1:
  280. case 2:
  281. case 3:
  282. return Colors.greenAccent;
  283. case 4:
  284. case 5:
  285. case 6:
  286. case 7:
  287. return Colors.redAccent;
  288. default:
  289. return Colors.lightBlue;
  290. }
  291. /// 肝脏是0:未见异常; 1、2、3、4、5、6、7、8良性;
  292. case DiagnosisOrganEnum.Liver:
  293. switch (label) {
  294. case 0:
  295. return Colors.lightBlue;
  296. case 4:
  297. return Colors.redAccent;
  298. case 1:
  299. case 2:
  300. case 3:
  301. case 5:
  302. case 6:
  303. case 7:
  304. case 8:
  305. return Colors.greenAccent;
  306. default:
  307. return Colors.lightBlue;
  308. }
  309. /// 甲状腺是0:未见异常; 1、2、3、4、5、6、7、8良性;
  310. case DiagnosisOrganEnum.Thyroid:
  311. switch (label) {
  312. case 0:
  313. return Colors.lightBlue;
  314. case 1:
  315. case 2:
  316. case 7:
  317. return Colors.greenAccent;
  318. case 3:
  319. case 4:
  320. case 5:
  321. case 6:
  322. return Colors.redAccent;
  323. default:
  324. return Colors.lightBlue;
  325. }
  326. default:
  327. return Colors.lightBlue;
  328. }
  329. }
  330. Widget _buildAITitle() {
  331. switch (aiPatintController.diagnosisOrgan) {
  332. case DiagnosisOrganEnum.Breast:
  333. return Obx(() {
  334. aiDetectedObjectItem = widget
  335. .aiDetectedObject?[aiPatintController.state.aiResultIndex] ??
  336. AIDetectedObject();
  337. return _buildBreastDescription(aiDetectedObjectItem.label);
  338. });
  339. case DiagnosisOrganEnum.Liver:
  340. return Obx(() {
  341. aiDetectedObjectItem = widget
  342. .aiDetectedObject?[aiPatintController.state.aiResultIndex] ??
  343. AIDetectedObject();
  344. return _buildLiverDescription(aiDetectedObjectItem.label);
  345. });
  346. case DiagnosisOrganEnum.Thyroid:
  347. return Obx(() {
  348. aiDetectedObjectItem = widget
  349. .aiDetectedObject?[aiPatintController.state.aiResultIndex] ??
  350. AIDetectedObject();
  351. return _buildThyroidDescription(aiDetectedObjectItem.label);
  352. });
  353. case DiagnosisOrganEnum.CarotidArtery:
  354. return Obx(() {
  355. aiDetectedObjectItem = widget
  356. .aiDetectedObject?[aiPatintController.state.aiResultIndex] ??
  357. AIDetectedObject();
  358. return _buildCarotidArteryDescription(aiDetectedObjectItem.label);
  359. });
  360. default:
  361. return const SizedBox();
  362. }
  363. }
  364. Widget _buildBreastDescription(int label) {
  365. switch (label) {
  366. case 0:
  367. return _buildDescription(
  368. i18nBook.measure.noSignificantAbnormalitiesWereSeen.t);
  369. case 1:
  370. return _buildDescription(i18nBook.measure.lipoma.t);
  371. case 2:
  372. return _buildDescription('BI-RADS 2');
  373. case 3:
  374. return _buildDescription('BI-RADS 3');
  375. case 4:
  376. return _buildDescription('BI-RADS 4a');
  377. case 5:
  378. return _buildDescription('BI-RADS 4b');
  379. case 6:
  380. return _buildDescription('BI-RADS 4c');
  381. case 7:
  382. return _buildDescription('BI-RADS 5');
  383. case 8:
  384. return _buildDescription(
  385. i18nBook.measure.noSignificantAbnormalitiesWereSeen.t);
  386. default:
  387. return _buildDescription(null);
  388. }
  389. }
  390. Widget _buildLiverDescription(int label) {
  391. switch (label) {
  392. case 0:
  393. return _buildDescription(
  394. i18nBook.measure.noSignificantAbnormalitiesWereSeen.t);
  395. case 1:
  396. return _buildDescription(i18nBook.measure.intrahepaticStrongEchoFoci.t);
  397. case 2:
  398. return _buildDescription(i18nBook.measure.hepaticHemangioma.t);
  399. case 3:
  400. return _buildDescription(i18nBook.measure.liverCysts.t);
  401. case 4:
  402. return _buildDescription(i18nBook.measure.liverCancerMayOccur.t);
  403. case 5:
  404. return _buildDescription(i18nBook.measure.fattyLiver.t);
  405. case 6:
  406. return _buildDescription(
  407. i18nBook.measure.panisodicChangesLiverDiffuseLesions.t);
  408. case 7:
  409. return _buildDescription(i18nBook.measure.cirrhosis.t);
  410. case 8:
  411. return _buildDescription(i18nBook.measure.polycysticLiver.t);
  412. default:
  413. return _buildDescription(null);
  414. }
  415. }
  416. Widget _buildThyroidDescription(int label) {
  417. switch (label) {
  418. case 0:
  419. return _buildDescription(
  420. i18nBook.measure.noSignificantAbnormalitiesWereSeen.t);
  421. case 1:
  422. return _buildDescription('TIRADS2');
  423. case 2:
  424. return _buildDescription('TIRADS3');
  425. case 3:
  426. return _buildDescription('TIRADS4a');
  427. case 4:
  428. return _buildDescription('TIRADS4b');
  429. case 5:
  430. return _buildDescription('TIRADS4c');
  431. case 6:
  432. return _buildDescription('TIRADS5');
  433. case 7:
  434. return _buildDescription(i18nBook.measure.presenceDiffuseDisease.t);
  435. default:
  436. return _buildDescription(null);
  437. }
  438. }
  439. Widget _buildDescription(
  440. String? title,
  441. ) {
  442. return Column(
  443. crossAxisAlignment: CrossAxisAlignment.start,
  444. children: [
  445. if (title != null) ...[
  446. const SizedBox(
  447. height: 5,
  448. ),
  449. Text(
  450. title,
  451. style: const TextStyle(color: Colors.white),
  452. ),
  453. ],
  454. ],
  455. );
  456. }
  457. Widget _buildCarotidArteryDescription(int label) {
  458. switch (label) {
  459. case 0:
  460. return _buildDescription(
  461. i18nBook.measure.noSignificantAbnormalitiesWereSeen.t);
  462. case 1:
  463. return _buildDescription(i18nBook.measure.patch.t);
  464. default:
  465. return _buildDescription(null);
  466. }
  467. }
  468. }