123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- part of 'control_board.dart';
- class _AIKeyFrame {
- final double index;
- final String name;
- _AIKeyFrame({
- required this.index,
- required this.name,
- });
- }
- class _ProgressBar extends StatefulWidget {
- @override
- State<StatefulWidget> createState() => _ProgressBarState();
- }
- class _ProgressBarState extends State<_ProgressBar> {
- final playerController = Get.find<IPlayerController>() as VidPlayerController;
- /// 测量AI数据
- final measureData = Get.find<MeasureDataController>();
- /// ai结果
- late final List<AIDiagnosisPerImageDTO> aiResult = [];
- late List<_AIKeyFrame> aiKeyFrame = [];
- late DiagnosisOrganEnum diagnosisOrgan = DiagnosisOrganEnum.Null;
- late String diagnosisOrganName = '';
- late final aiPatintController = Get.find<AiPatintController>();
- double curCursorIndex = -10;
- static const marginSpace = 10.0;
- /// 获取关键帧
- void getKeyFrame() {
- bool canShowAI = [
- DiagnosisConclusionEnum.Benign,
- DiagnosisConclusionEnum.Malignant,
- DiagnosisConclusionEnum.BenignAndMalignant
- ].contains(measureData.diagnosisConclusion);
- if (canShowAI) {
- final measureDataAIResults = jsonDecode(
- measureData.aiResults,
- );
- aiResult.clear();
- for (int i = 0; i < (measureDataAIResults as List).length; i++) {
- aiResult.add(
- AIDiagnosisPerImageDTO.fromJson(
- measureDataAIResults[i],
- ),
- );
- }
- for (int j = 0; j < aiResult.length; j++) {
- List<AIDiagnosisResultPerOrgan> diagResultsForEachOrgan =
- aiResult[j].diagResultsForEachOrgan ?? [];
- if (diagResultsForEachOrgan.isNotEmpty) {
- List<AIDetectedObject> detectedObjects =
- diagResultsForEachOrgan[0].detectedObjects ?? [];
- diagnosisOrgan = diagResultsForEachOrgan[0].organ;
- bool isLiver = diagnosisOrgan == DiagnosisOrganEnum.Liver;
- bool isThyroid = diagnosisOrgan == DiagnosisOrganEnum.Thyroid;
- diagnosisOrganName = '';
- List<String> diagnosisOrganLabel = [];
- for (int m = 0; m < detectedObjects.length; m++) {
- if (detectedObjects[m].label != 0) {
- diagnosisOrganLabel.add(
- _buildAITitle(detectedObjects[m].label),
- );
- if (isLiver) {
- diagnosisOrganName = diagnosisOrganLabel.toSet().join(",");
- if (detectedObjects[m].label < 5) {
- aiKeyFrame.add(
- _AIKeyFrame(
- name: diagnosisOrganName,
- index: j.toDouble(),
- ),
- );
- }
- } else if (isThyroid) {
- diagnosisOrganName = diagnosisOrganLabel.toSet().join(",");
- if (detectedObjects[m].label < 7) {
- aiKeyFrame.add(
- _AIKeyFrame(
- name: diagnosisOrganName,
- index: j.toDouble(),
- ),
- );
- }
- } else {
- diagnosisOrganName = diagnosisOrganLabel.toSet().join(",");
- aiKeyFrame.add(
- _AIKeyFrame(
- name: diagnosisOrganName,
- index: j.toDouble(),
- ),
- );
- }
- }
- }
- }
- }
- }
- }
- void onMeasuredAIResultsInfoChanged(Object sender, String e) {
- aiKeyFrame = [];
- if (e.isNotEmpty && e != "[]") {
- getKeyFrame();
- } else {
- setState(() {
- aiResult.clear();
- });
- }
- }
- @override
- void initState() {
- WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- if (mounted) {
- playerController.eventHandler.addListener(onControllerEvent);
- getKeyFrame();
- }
- });
- measureData.aiResultsInfoChanged
- .addListener(onMeasuredAIResultsInfoChanged);
- super.initState();
- }
- @override
- void dispose() {
- measureData.aiResultsInfoChanged
- .removeListener(onMeasuredAIResultsInfoChanged);
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final controller = playerController;
- var index = controller.currentFrameIndex.toDouble();
- var max = 100.0;
- if (index < 0) {
- index = 0;
- } else {
- /// 第一帧下标为0 ,所以max要减1
- max = controller.totalFramesCount.toDouble() - 1;
- }
- return SliderTheme(
- data: const SliderThemeData(
- trackHeight: 12,
- thumbColor: Colors.white,
- trackShape: _FullWidthTrackShape(),
- ),
- child: LayoutBuilder(builder: (
- BuildContext layoutBuilderContext,
- BoxConstraints constraints,
- ) {
- final BoxConstraints insideConstraints = constraints.copyWith(
- maxWidth: constraints.maxWidth - marginSpace,
- );
- return Stack(
- alignment: Alignment.center,
- children: [
- // const CrossFrameAnchorBar(),
- const StreamingProgressBarWithCrossFrame(),
- Container(
- padding: const EdgeInsets.only(
- left: marginSpace / 2,
- ),
- child: Stack(
- alignment: Alignment.center,
- children: [
- ...aiKeyFrame.map((i) {
- return Obx(() {
- if (aiPatintController.state.ifShowAi) {
- return _PanelPoint(
- max: max,
- index: index,
- constraints: insideConstraints,
- currentIndex: i.index,
- onChanged: () {
- controller.locateTo(i.index.toInt());
- },
- aiDiagnosticOrganName: i.name,
- curCursorIndex: curCursorIndex,
- );
- } else {
- return const SizedBox();
- }
- });
- }).toList(),
- _CursorTrack(
- constraints: insideConstraints,
- cursorUpdateCallback: (localPositionX) {
- setState(() {
- curCursorIndex =
- (localPositionX / insideConstraints.maxWidth * max)
- .roundToDouble();
- });
- },
- cursorExit: () {
- setState(() {
- curCursorIndex = -10;
- });
- },
- )
- ],
- ),
- ),
- ],
- );
- }),
- );
- }
- void onControllerEvent(Object sender, VidPlayerEvent e) {
- if (e is VidPlayerFrameIndexChangeEvent) {
- onPlayFrameIndexChanged(e);
- }
- }
- void onPlayFrameIndexChanged(VidPlayerFrameIndexChangeEvent e) {
- if (mounted) {
- setState(() {});
- }
- }
- String _buildAITitle(int label) {
- switch (diagnosisOrgan) {
- case DiagnosisOrganEnum.Breast:
- return _buildBreastDescription(label);
- case DiagnosisOrganEnum.Liver:
- return _buildLiverDescription(label);
- case DiagnosisOrganEnum.Thyroid:
- return _buildThyroidDescription(label);
- default:
- return '';
- }
- }
- String _buildBreastDescription(int label) {
- switch (label) {
- case 0:
- return i18nBook.measure.noSignificantAbnormalitiesWereSeen.t;
- case 1:
- return i18nBook.measure.lipoma.t;
- case 2:
- return 'BI-RADS 2';
- case 3:
- return 'BI-RADS 3';
- case 4:
- return 'BI-RADS 4a';
- case 5:
- return 'BI-RADS 4b';
- case 6:
- return 'BI-RADS 4c';
- case 7:
- return 'BI-RADS 5';
- case 8:
- return i18nBook.measure.noSignificantAbnormalitiesWereSeen.t;
- default:
- return '';
- }
- }
- String _buildLiverDescription(int label) {
- switch (label) {
- case 0:
- return i18nBook.measure.noSignificantAbnormalitiesWereSeen.t;
- case 1:
- return i18nBook.measure.intrahepaticStrongEchoFoci.t;
- case 2:
- return i18nBook.measure.hepaticHemangioma.t;
- case 3:
- return i18nBook.measure.liverCysts.t;
- case 4:
- return i18nBook.measure.liverCancerMayOccur.t;
- case 5:
- return i18nBook.measure.fattyLiver.t;
- case 6:
- return i18nBook.measure.panisodicChangesLiverDiffuseLesions.t;
- case 7:
- return i18nBook.measure.cirrhosis.t;
- case 8:
- return i18nBook.measure.polycysticLiver.t;
- default:
- return '';
- }
- }
- String _buildThyroidDescription(int label) {
- switch (label) {
- case 0:
- return i18nBook.measure.noSignificantAbnormalitiesWereSeen.t;
- case 1:
- return 'TIRADS2';
- case 2:
- return 'TIRADS3';
- case 3:
- return 'TIRADS4a';
- case 4:
- return 'TIRADS4b';
- case 5:
- return 'TIRADS4c';
- case 6:
- return 'TIRADS5';
- case 7:
- return i18nBook.measure.presenceDiffuseDisease.t;
- default:
- return '';
- }
- }
- Widget _buildDescription(
- String? title,
- ) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (title != null) ...[
- const SizedBox(
- height: 5,
- ),
- Text(
- title,
- style: const TextStyle(color: Colors.white),
- ),
- ],
- ],
- );
- }
- // String _buildAIDiagnosticOrgans() {
- // switch (diagnosisOrgan) {
- // case DiagnosisOrganEnum.Breast:
- // return i18nBook.remedical.breast.t;
- // case DiagnosisOrganEnum.Abdomen:
- // return i18nBook.bodyParts.abdomen.t;
- // case DiagnosisOrganEnum.Liver:
- // return i18nBook.remedical.liver.t;
- // case DiagnosisOrganEnum.Cholecyst:
- // return i18nBook.remedical.cholecyst.t;
- // case DiagnosisOrganEnum.Kidney:
- // return i18nBook.remedical.kidney.t;
- // case DiagnosisOrganEnum.Spleen:
- // return i18nBook.remedical.spleen.t;
- // default:
- // return '';
- // }
- // }
- }
- // https://juejin.cn/post/6959703051586240549
- class _FullWidthTrackShape extends RoundedRectSliderTrackShape {
- const _FullWidthTrackShape();
- @override
- Rect getPreferredRect({
- required RenderBox parentBox,
- Offset offset = Offset.zero,
- required SliderThemeData sliderTheme,
- bool isEnabled = false,
- bool isDiscrete = false,
- }) {
- final double trackHeight = sliderTheme.trackHeight ?? 2;
- final double trackLeft = offset.dx;
- final double trackTop =
- offset.dy + (parentBox.size.height - trackHeight) / 2;
- // 让轨道宽度等于 Slider 宽度
- final double trackWidth = parentBox.size.width;
- return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
- }
- }
- class MeasureTooltip extends StatelessWidget {
- final String tooltips;
- final FWidget child;
- const MeasureTooltip({
- Key? key,
- required this.tooltips,
- required this.child,
- }) : super(key: key);
- @override
- Widget build(BuildContext context) {
- return FMaterialTooltip(
- message: tooltips,
- preferBelow: false,
- padding: const EdgeInsets.all(8),
- textStyle: const TextStyle(
- fontSize: 16,
- color: Colors.white,
- ),
- triggerMode: TooltipTriggerMode.tap, // 单击 显示Tooltip
- verticalOffset: 30,
- child: child,
- );
- }
- }
- /// AI节点
- class _PanelPoint extends StatefulWidget {
- const _PanelPoint({
- required this.max,
- required this.index,
- required this.constraints,
- required this.currentIndex,
- required this.onChanged,
- required this.curCursorIndex,
- required this.aiDiagnosticOrganName,
- });
- final double max;
- final double index;
- final BoxConstraints constraints;
- final double currentIndex;
- final VoidCallback onChanged;
- final double curCursorIndex;
- final String aiDiagnosticOrganName;
- @override
- State<_PanelPoint> createState() => _PanelPointState();
- }
- class _PanelPointState extends State<_PanelPoint> {
- FWidget marker({
- required double size,
- required Color color,
- }) {
- return FInkWell(
- onTap: widget.onChanged,
- child: FContainer(
- height: size,
- width: size,
- decoration: BoxDecoration(
- color: color,
- borderRadius: BorderRadius.circular(size / 2),
- ),
- ),
- );
- }
- @override
- Widget build(BuildContext context) {
- double size =
- max(8 - (widget.curCursorIndex - widget.currentIndex).abs(), 3);
- return Positioned(
- left: widget.currentIndex / widget.max * widget.constraints.maxWidth -
- (size / 2),
- top: widget.constraints.maxHeight / 2 + 15 + size,
- child: MeasureTooltip(
- tooltips: widget.aiDiagnosticOrganName,
- child: marker(
- size: size,
- color: widget.currentIndex == widget.index
- ? const Color.fromARGB(255, 255, 111, 45)
- : const Color.fromARGB(255, 252, 255, 45),
- ),
- ),
- );
- }
- }
- class _CursorTrack extends StatelessWidget {
- const _CursorTrack({
- required this.constraints,
- required this.cursorUpdateCallback,
- required this.cursorExit,
- });
- final BoxConstraints constraints;
- final ValueCallback<double> cursorUpdateCallback;
- final VoidCallback cursorExit;
- @override
- Widget build(BuildContext context) {
- return Positioned(
- top: constraints.maxHeight / 2 + 10,
- child: MouseRegion(
- onHover: (e) {
- cursorUpdateCallback(e.localPosition.dx);
- },
- onExit: (e) => {cursorExit()},
- opaque: false,
- child: SizedBox(
- width: constraints.maxWidth,
- height: 40,
- child: Container(),
- ),
- ),
- );
- }
- }
- class _CrossFrameAnchorProgress extends StatefulWidget {
- @override
- State<StatefulWidget> createState() => _CrossFrameAnchorProgressState();
- }
- class _CrossFrameAnchorProgressState extends State<_CrossFrameAnchorProgress> {
- final controller = Get.find<IPlayerController>() as VidPlayerController;
- @override
- void initState() {
- // TODO: implement initState
- super.initState();
- }
- @override
- Widget build(BuildContext context) {
- return SizedBox();
- }
- }
|