HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“医智助手“——医疗影像AI智能体辅助诊断平台
2026年,医疗智能体已成为垂直领域最火热的应用方向。医疗智能体提供智能问诊、临床决策支持、医学影像分析、患者管理等服务,基于大规模真实医学数据与临床案例进行训练,在影像判读、智能问诊、临床决策支持等场景中全方位提升医疗服务质量和效率。传统医疗影像诊断往往采用固定的PACS界面和静态报告,医生需要手动调阅影像、逐层分析、再手动撰写报告。而在HarmonyOS 6的PC大屏环境下,这种被动式诊断模式
文章目录

每日一句正能量
低谷期并不意味着无法前行,相反,它是你自我觉醒的关键时刻。
低谷不是行动的终点,而是转变的起点。许多重大突破恰恰发生在看似停滞的时期,因为此时旧模式瓦解,新认知才有空间诞生。
前言
摘要:医疗影像领域存在海量数据处理压力,传统人工阅片存在效率低、误诊率高等问题。HarmonyOS 6(API 23)引入的鸿蒙智能体框架(HMAF)配合端侧AI能力,为医疗影像智能分析提供了全新可能。本文将实战开发一款面向HarmonyOS PC的"医智助手"应用,展示如何利用HMAF构建"影像采集-病灶检测-诊断推理-报告生成"四层智能体协作架构,通过悬浮导航实现科室与病例状态实时切换,基于沉浸光感打造"病灶即氛围"的诊疗感知体验,以及基于多窗口架构构建浮动影像视窗、病灶标注面板和诊断报告窗口的协作诊疗体验。
一、前言:AI智能体时代的医疗影像革新
2026年,医疗智能体已成为垂直领域最火热的应用方向。医疗智能体提供智能问诊、临床决策支持、医学影像分析、患者管理等服务,基于大规模真实医学数据与临床案例进行训练,在影像判读、智能问诊、临床决策支持等场景中全方位提升医疗服务质量和效率。
传统医疗影像诊断往往采用固定的PACS界面和静态报告,医生需要手动调阅影像、逐层分析、再手动撰写报告。而在HarmonyOS 6的PC大屏环境下,这种被动式诊断模式显得低效且缺乏智能辅助。HMAF配合**悬浮导航(Float Navigation)与沉浸光感(Immersive Light Effects)**特性,为医疗影像AI诊断带来了"病灶即氛围、异常即导航"的全新可能。
本文核心亮点:
- 病灶风险光效:根据病灶风险等级(良性/低危/中危/高危/恶性)动态切换环境光色与脉冲节奏
- 悬浮科室导航:底部悬浮页签替代传统工具栏,支持科室切换、病例筛选、诊断状态实时徽章
- HMAF四层诊疗架构:基于Agent Framework Kit构建"影像采集-病灶检测-诊断推理-报告生成"四层智能体协作架构
- 多窗口协作诊疗:主影像视窗 + 浮动病灶标注面板 + 浮动诊断报告窗口 + 浮动参考病例窗口的光效联动
- 诊断意图沉浸感知:通过Intents Kit实时理解医生诊断意图,自动调整界面光效与导航形态
二、核心特性解析与技术选型
2.1 HMAF在医疗影像诊断中的价值
HarmonyOS 6的HMAF采用四层架构设计:应用智能体层、智能体框架层、AI引擎层、智能体内核层。 在"医智助手"中,这种架构能够:
- 原生智能调度:医疗智能体不再是应用的附属品,而是系统的基础能力,支持跨科室任务编排
- 意图即诊断:通过Intents Kit将医生自然语言意图(如"查看肺部CT结节")转化为结构化诊断任务
- 分布式智能体协同:利用鸿蒙分布式软总线,实现PC主控+平板阅片+手机随访的多设备协作
- 端云协同推理:端侧MindSpore Lite处理实时影像预处理,云端大模型处理复杂病灶识别
2.2 沉浸光感在医疗诊断中的创新应用
HarmonyOS 6的systemMaterialEffect通过模拟物理光照模型,为诊断状态反馈带来细腻的视觉表达。在医疗影像场景中,这种材质效果能够:
- 增强病灶感知:不同风险等级拥有专属光效标识(良性绿、低危蓝、中危黄、高危橙、恶性红)
- 状态直觉感知:分析时的呼吸蓝光、发现病灶时的脉冲金光、高危病灶时的警示红光
- 提升诊断专注度:动态环境光随影像复杂度变化,简单病例柔和、复杂病例强烈
2.3 悬浮导航的医疗适配
与传统PACS系统不同,医疗影像诊断平台需要处理:
- 高频科室切换:医生常在多个科室间快速跳转查看病例
- 信息密度平衡:既要保证导航可见,又不能遮挡影像内容
- 实时诊断徽章:诊断任务运行状态需要实时徽章提示(分析中脉冲、待确认角标、已完成确认)
HarmonyOS 6的悬浮页签支持**强(85%)、平衡(70%)、弱(55%)**三档透明度自定义,结合PC端的自由窗口能力,可以实现"需要时出现,专注时隐退"的智能导航体验。
三、项目实战:"医智助手"架构设计
3.1 应用场景与功能规划
面向HarmonyOS PC的医疗影像AI诊断场景,核心功能包括:
| 功能模块 | 技术实现 | 沉浸光感/HMAF应用 |
|---|---|---|
| 主影像视窗 | Canvas + DICOM解析 |
病灶高亮光效、影像增强呼吸灯 |
| 悬浮科室导航 | HdsTabs + systemMaterialEffect |
玻璃拟态页签,诊断状态徽章 |
| 影像采集智能体 | HMAF Agent Framework Kit | 采集进度光效反馈 |
| 病灶检测智能体 | HMAF + 3D UNet | 检测进度光效脉冲 |
| 诊断推理智能体 | HMAF + 知识图谱 | 推理结果光效标记 |
| 报告生成智能体 | HMAF + NLG | 报告完成光效提示 |
| 浮动病灶标注面板 | 子窗口 + HdsNavigation |
病灶主题色光效同步 |
| 浮动诊断报告窗口 | 子窗口 + RichText |
诊断结果颜色编码 |
| 浮动参考病例窗口 | 子窗口 + Grid |
相似病例匹配度光效 |
3.2 技术架构图
┌─────────────────────────────────────────────────────────────┐
│ 医智助手 - 应用层 │
├─────────────┬─────────────┬─────────────┬─────────────────────┤
│ 影像采集智能体 │ 病灶检测智能体 │ 诊断推理智能体 │ 报告生成智能体 │
│ (ImageAcq) │ (LesionDet) │ (Diagnosis) │ (ReportGen) │
├─────────────┴─────────────┴─────────────┴─────────────────────┤
│ HMAF - 智能体框架层 │
│ Agent Framework Kit + Intents Kit + NLU Kit │
├─────────────────────────────────────────────────────────────┤
│ AI引擎层 - 能力层 │
│ MindSpore Lite(端侧) │ 鸿蒙大模型4.0(云端) │ 分布式软总线 │
├─────────────────────────────────────────────────────────────┤
│ ArkUI - 表现层 │
│ 悬浮导航 │ 沉浸光感 │ 多窗口管理 │ 安全区扩展 │ 动画系统 │
└─────────────────────────────────────────────────────────────┘
四、环境配置与模块依赖
4.1 模块依赖配置
在oh-package.json5中添加HMAF及相关Kit依赖:
{
"name": "medical_assistant",
"version": "1.0.0",
"description": "Medical Imaging AI Agent Diagnosis Platform",
"dependencies": {
"@ohos/hmaframework": "^6.0.0",
"@ohos/intentskit": "^6.0.0",
"@ohos/nlukit": "^6.0.0",
"@ohos/mindsporelite": "^6.0.0",
"@ohos/aiengine": "^6.0.0",
"@ohos/hds": "^6.0.0",
"@ohos/medicalkit": "^6.0.0"
}
}
4.2 权限声明(module.json5)
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet", "2in1"],
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["action.system.home"]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:internet_permission"
},
{
"name": "ohos.permission.SYSTEM_FLOAT_WINDOW",
"reason": "$string:float_window_permission",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC",
"reason": "$string:distributed_permission"
},
{
"name": "ohos.permission.AI_ENGINE_ACCESS",
"reason": "$string:ai_engine_permission"
},
{
"name": "ohos.permission.READ_FILE",
"reason": "$string:read_file_permission"
},
{
"name": "ohos.permission.WRITE_FILE",
"reason": "$string:write_file_permission"
}
]
}
}
五、核心组件实战
5.1 窗口沉浸配置(EntryAbility.ets)
// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
export default class EntryAbility extends UIAbility {
private windowStage: window.WindowStage | null = null;
onWindowStageCreate(windowStage: window.WindowStage): void {
this.windowStage = windowStage;
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
console.error('Failed to load content:', JSON.stringify(err));
return;
}
console.info('Succeeded in loading content.');
this.setupImmersiveWindow(windowStage);
});
}
private async setupImmersiveWindow(windowStage: window.WindowStage): Promise<void> {
try {
const mainWindow = windowStage.getMainWindowSync();
await mainWindow.setWindowLayoutFullScreen(true);
await mainWindow.setWindowBackgroundColor('#00000000');
await mainWindow.setWindowSystemBarProperties({
statusBarColor: '#00000000',
navigationBarColor: '#00000000',
statusBarContentColor: '#FFFFFF',
navigationBarContentColor: '#FFFFFF'
});
await mainWindow.setWindowAvoidAreaOption({
type: window.AvoidAreaType.TYPE_SYSTEM,
enabled: true
});
console.info('Immersive window setup completed');
} catch (error) {
console.error('Failed to setup immersive window:', (error as BusinessError).message);
}
}
onWindowStageDestroy(): void {
this.windowStage = null;
}
}
代码亮点:通过setWindowLayoutFullScreen(true)实现内容延伸至非安全区,配合setWindowAvoidAreaOption启用HarmonyOS 6新增的安全区避让机制,确保悬浮导航不会遮挡影像内容。
5.2 病灶风险光效系统(LesionLightEffect.ets)
代码亮点:这是本应用的核心创新组件。它将病灶风险等级映射为动态光效——良性时柔和绿光、低危时平静蓝光、中危时警示黄光、高危时脉冲橙光、恶性时闪烁红光。每个风险等级拥有专属脉冲节奏与光晕强度,通过AppStorage全局状态实现跨组件光效同步。
// entry/src/main/ets/components/LesionLightEffect.ets
import { hmaf } from '@kit.HMAFramework';
// 病灶风险等级枚举
export enum RiskLevel {
BENIGN = 'benign', // 良性
LOW = 'low', // 低危
MEDIUM = 'medium', // 中危
HIGH = 'high', // 高危
MALIGNANT = 'malignant' // 恶性
}
// 风险等级色彩配置
export const RiskColors: Record<RiskLevel, string> = {
[RiskLevel.BENIGN]: '#2ECC71', // 良性绿
[RiskLevel.LOW]: '#4A90D9', // 低危蓝
[RiskLevel.MEDIUM]: '#F5A623', // 中危黄
[RiskLevel.HIGH]: '#FF6B6B', // 高危橙
[RiskLevel.MALIGNANT]: '#FF0000' // 恶性红
};
// 光效配置
interface LightEffectConfig {
baseColor: string;
pulseSpeed: number; // 脉冲周期(ms)
pulseIntensity: number; // 脉冲强度(0-1)
glowRadius: number; // 光晕半径
alertMode: boolean; // 是否启用警示模式
}
@Component
export struct LesionLightEffect {
@State currentRisk: RiskLevel = RiskLevel.BENIGN;
@State lightIntensity: number = 0.4;
@State pulsePhase: number = 0;
@State alertFlash: boolean = false;
private pulseTimer: number = -1;
private alertTimer: number = -1;
aboutToAppear(): void {
this.startPulseAnimation();
// 监听全局风险等级变化
AppStorage.setOrCreate('lesion_risk_change', (level: RiskLevel) => {
this.currentRisk = level;
this.updateLightEffect();
});
}
aboutToDisappear(): void {
clearInterval(this.pulseTimer);
clearInterval(this.alertTimer);
}
private startPulseAnimation(): void {
this.pulseTimer = setInterval(() => {
this.pulsePhase = (this.pulsePhase + 0.05) % (Math.PI * 2);
this.lightIntensity = this.calculatePulseIntensity();
}, 50);
}
private calculatePulseIntensity(): number {
const baseIntensity = this.getRiskConfig().pulseIntensity;
const variation = Math.sin(this.pulsePhase) * 0.3;
return Math.max(0.1, Math.min(1.0, baseIntensity + variation));
}
private getRiskConfig(): LightEffectConfig {
const configs: Record<RiskLevel, LightEffectConfig> = {
[RiskLevel.BENIGN]: {
baseColor: RiskColors[RiskLevel.BENIGN],
pulseSpeed: 4000,
pulseIntensity: 0.4,
glowRadius: 80,
alertMode: false
},
[RiskLevel.LOW]: {
baseColor: RiskColors[RiskLevel.LOW],
pulseSpeed: 3000,
pulseIntensity: 0.5,
glowRadius: 100,
alertMode: false
},
[RiskLevel.MEDIUM]: {
baseColor: RiskColors[RiskLevel.MEDIUM],
pulseSpeed: 2000,
pulseIntensity: 0.7,
glowRadius: 120,
alertMode: false
},
[RiskLevel.HIGH]: {
baseColor: RiskColors[RiskLevel.HIGH],
pulseSpeed: 1000,
pulseIntensity: 0.9,
glowRadius: 150,
alertMode: true
},
[RiskLevel.MALIGNANT]: {
baseColor: RiskColors[RiskLevel.MALIGNANT],
pulseSpeed: 500,
pulseIntensity: 1.0,
glowRadius: 200,
alertMode: true
}
};
return configs[this.currentRisk] || configs[RiskLevel.BENIGN];
}
private updateLightEffect(): void {
const config = this.getRiskConfig();
AppStorage.setOrCreate('current_lesion_color', config.baseColor);
AppStorage.setOrCreate('current_lesion_pulse', config.pulseSpeed);
AppStorage.setOrCreate('current_lesion_intensity', config.pulseIntensity);
// 高危/恶性级别启用警示闪烁
if (config.alertMode) {
this.startAlertFlash();
} else {
clearInterval(this.alertTimer);
this.alertFlash = false;
}
}
private startAlertFlash(): void {
this.alertTimer = setInterval(() => {
this.alertFlash = !this.alertFlash;
}, this.getRiskConfig().pulseSpeed / 2);
}
build() {
Stack() {
// 主光晕层
Column()
.width(400)
.height(400)
.backgroundColor(this.getRiskConfig().baseColor)
.blur(this.getRiskConfig().glowRadius)
.opacity(this.alertFlash ? this.lightIntensity * 1.5 : this.lightIntensity)
.position({ x: '50%', y: '30%' })
.anchor('50%')
.animation({
duration: this.getRiskConfig().pulseSpeed,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
// 警示闪烁层(高危/恶性)
if (this.getRiskConfig().alertMode) {
Column()
.width('100%')
.height('100%')
.backgroundColor(this.alertFlash ? 'rgba(255,0,0,0.1)' : 'transparent')
.animation({
duration: this.getRiskConfig().pulseSpeed / 2,
curve: Curve.Step,
iterations: -1
})
}
// 风险指示器环
Column()
.width(200)
.height(200)
.backgroundColor('transparent')
.border({
width: 3,
color: this.getRiskConfig().baseColor,
style: BorderStyle.Solid
})
.borderRadius(100)
.opacity(this.lightIntensity * 0.5)
.animation({
duration: this.getRiskConfig().pulseSpeed,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
}
.width('100%')
.height('100%')
}
}
5.3 HMAF四层诊疗智能体架构(DiagnosisAgentScheduler.ets)
代码亮点:基于HMAF Agent Framework Kit构建核心诊疗调度逻辑,实现"影像采集→病灶检测→诊断推理→报告生成"的完整诊疗工作流。调度器通过Intents Kit解析医生自然语言意图,自动创建四层智能体实例,并实时监控各智能体状态。
// entry/src/main/ets/services/DiagnosisAgentScheduler.ets
import { hmaf } from '@kit.HMAFramework';
import { intents } from '@kit.IntentsKit';
import { nlu } from '@kit.NLUKit';
// 诊疗任务定义
interface DiagnosisTask {
id: string;
taskType: 'image_acquisition' | 'lesion_detection' | 'diagnosis_reasoning' | 'report_generation';
patientId: string;
studyId: string;
modality: string;
parameters: Record<string, unknown>;
priority: number;
deadline: number;
}
// 病灶发现
interface Lesion {
id: string;
type: string;
location: string;
size: number;
riskLevel: RiskLevel;
confidence: number;
boundingBox: { x: number; y: number; width: number; height: number };
}
// 智能体实例
interface AgentInstance {
id: string;
type: 'image_acq' | 'lesion_det' | 'diagnosis' | 'report_gen';
status: 'idle' | 'acquiring' | 'detecting' | 'reasoning' | 'generating' | 'completed' | 'error';
currentTask: DiagnosisTask | null;
capability: string[];
}
export class DiagnosisAgentScheduler {
private static instance: DiagnosisAgentScheduler;
private agents: Map<string, AgentInstance> = new Map();
private taskQueue: DiagnosisTask[] = [];
private hmafSession: hmaf.AgentSession | null = null;
private intentEngine: intents.IntentEngine | null = null;
private lesions: Lesion[] = [];
private constructor() {}
static getInstance(): DiagnosisAgentScheduler {
if (!DiagnosisAgentScheduler.instance) {
DiagnosisAgentScheduler.instance = new DiagnosisAgentScheduler();
}
return DiagnosisAgentScheduler.instance;
}
async initialize(): Promise<void> {
try {
// 初始化HMAF会话
this.hmafSession = await hmaf.createAgentSession({
mode: hmaf.AgentMode.MULTI_AGENT,
maxConcurrentAgents: 8,
enableDistributed: true
});
// 初始化意图引擎
this.intentEngine = await intents.createIntentEngine({
supportedDomains: ['medical_imaging', 'diagnosis', 'report_generation'],
language: 'zh-CN'
});
// 注册四层诊疗智能体
await this.registerDiagnosisAgents();
console.info('DiagnosisAgentScheduler initialized successfully');
} catch (error) {
console.error('Failed to initialize DiagnosisAgentScheduler:', error);
throw error;
}
}
private async registerDiagnosisAgents(): Promise<void> {
// 注册影像采集智能体
this.agents.set('image-acq-1', {
id: 'image-acq-1',
type: 'image_acq',
status: 'idle',
currentTask: null,
capability: ['dicom_parsing', 'image_preprocessing', '3d_reconstruction']
});
// 注册病灶检测智能体
this.agents.set('lesion-det-1', {
id: 'lesion-det-1',
type: 'lesion_det',
status: 'idle',
currentTask: null,
capability: ['lung_nodule', 'liver_tumor', 'brain_lesion', 'bone_fracture']
});
this.agents.set('lesion-det-2', {
id: 'lesion-det-2',
type: 'lesion_det',
status: 'idle',
currentTask: null,
capability: ['cardiac_analysis', 'vascular_segmentation', 'mammography']
});
// 注册诊断推理智能体
this.agents.set('diagnosis-1', {
id: 'diagnosis-1',
type: 'diagnosis',
status: 'idle',
currentTask: null,
capability: ['differential_diagnosis', 'knowledge_graph_reasoning', 'clinical_guideline_matching']
});
// 注册报告生成智能体
this.agents.set('report-gen-1', {
id: 'report-gen-1',
type: 'report_gen',
status: 'idle',
currentTask: null,
capability: ['structured_report', 'narrative_report', 'comparison_report']
});
}
// 处理医生诊断意图
async processDiagnosisIntent(userInput: string, imageData: ArrayBuffer): Promise<string> {
try {
// 更新状态为采集中
this.updateAgentState('image-acq-1', 'acquiring');
// 解析医生意图
const parsedIntent = await this.intentEngine?.parseIntent(userInput);
if (!parsedIntent) {
throw new Error('Failed to parse diagnosis intent');
}
// 创建诊疗任务链
const taskChain = await this.createTaskChain(parsedIntent, imageData);
// 执行影像采集
const acquiredImages = await this.executeImageAcquisition(taskChain);
// 执行病灶检测
const detectedLesions = await this.executeLesionDetection(acquiredImages);
// 执行诊断推理
const diagnosisResult = await this.executeDiagnosisReasoning(detectedLesions);
// 执行报告生成
const report = await this.executeReportGeneration(diagnosisResult);
// 更新状态为完成
this.updateAgentState('image-acq-1', 'idle');
return this.formatDiagnosisReport(detectedLesions, diagnosisResult, report);
} catch (error) {
this.updateAgentState('image-acq-1', 'error');
console.error('Error processing diagnosis intent:', error);
return `诊断异常:${error.message}`;
}
}
private async createTaskChain(intent: intents.ParsedIntent, imageData: ArrayBuffer): Promise<<DiagnosisTask[]> {
const tasks: DiagnosisTask[] = [];
const taskId = `diag-${Date.now()}`;
// 根据意图类型创建诊疗链
switch (intent.domain) {
case 'medical_imaging':
tasks.push({
id: `${taskId}-1`,
taskType: 'image_acquisition',
patientId: intent.parameters.patientId || 'unknown',
studyId: intent.parameters.studyId || 'unknown',
modality: intent.parameters.modality || 'CT',
parameters: { imageData, windowing: intent.parameters.windowing },
priority: 1,
deadline: Date.now() + 30000
});
tasks.push({
id: `${taskId}-2`,
taskType: 'lesion_detection',
patientId: intent.parameters.patientId || 'unknown',
studyId: intent.parameters.studyId || 'unknown',
modality: intent.parameters.modality || 'CT',
parameters: { organ: intent.parameters.organ || 'lung' },
priority: 2,
deadline: Date.now() + 60000
});
tasks.push({
id: `${taskId}-3`,
taskType: 'diagnosis_reasoning',
patientId: intent.parameters.patientId || 'unknown',
studyId: intent.parameters.studyId || 'unknown',
modality: intent.parameters.modality || 'CT',
parameters: { history: intent.parameters.history },
priority: 3,
deadline: Date.now() + 90000
});
tasks.push({
id: `${taskId}-4`,
taskType: 'report_generation',
patientId: intent.parameters.patientId || 'unknown',
studyId: intent.parameters.studyId || 'unknown',
modality: intent.parameters.modality || 'CT',
parameters: { template: intent.parameters.template || 'structured' },
priority: 4,
deadline: Date.now() + 120000
});
break;
default:
tasks.push({
id: `${taskId}-1`,
taskType: 'image_acquisition',
patientId: 'unknown',
studyId: 'unknown',
modality: 'CT',
parameters: { imageData },
priority: 1,
deadline: Date.now() + 30000
});
}
return tasks;
}
private async executeImageAcquisition(tasks: DiagnosisTask[]): Promise<<ArrayBuffer[]> {
const results: ArrayBuffer[] = [];
for (const task of tasks.filter(t => t.taskType === 'image_acquisition')) {
const agent = this.findAvailableAgent('image_acq');
if (!agent) continue;
this.updateAgentState(agent.id, 'acquiring');
// 通过HMAF执行影像采集
const acquisition = await this.hmafSession?.sendTask({
targetAgent: agent.id,
taskType: 'image_acquisition',
parameters: {
imageData: task.parameters.imageData,
modality: task.modality,
windowing: task.parameters.windowing
},
timeout: task.deadline - Date.now()
});
if (acquisition?.processedImages) {
results.push(...acquisition.processedImages);
}
this.updateAgentState(agent.id, 'idle');
}
return results;
}
private async executeLesionDetection(images: ArrayBuffer[]): Promise<<Lesion[]> {
const agent = this.findAvailableAgent('lesion_det');
if (!agent) return [];
this.updateAgentState(agent.id, 'detecting');
const detectedLesions: Lesion[] = [];
for (const image of images) {
const detection = await this.hmafSession?.sendTask({
targetAgent: agent.id,
taskType: 'lesion_detection',
parameters: {
imageData: image,
model: 'lung_nano_unet',
confidenceThreshold: 0.85
},
timeout: 30000
});
if (detection?.lesions) {
for (const lesion of detection.lesions) {
detectedLesions.push({
id: `lesion-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
type: lesion.type,
location: lesion.location,
size: lesion.size,
riskLevel: this.mapConfidenceToRisk(lesion.confidence, lesion.type),
confidence: lesion.confidence,
boundingBox: lesion.boundingBox
});
}
}
}
this.lesions = detectedLesions;
this.updateAgentState(agent.id, 'idle');
return detectedLesions;
}
private mapConfidenceToRisk(confidence: number, type: string): RiskLevel {
if (confidence > 0.95 && ['adenocarcinoma', 'squamous_cell'].includes(type)) {
return RiskLevel.MALIGNANT;
}
if (confidence > 0.90) {
return RiskLevel.HIGH;
}
if (confidence > 0.80) {
return RiskLevel.MEDIUM;
}
if (confidence > 0.70) {
return RiskLevel.LOW;
}
return RiskLevel.BENIGN;
}
private async executeDiagnosisReasoning(lesions: Lesion[]): Promise<<Record<string, unknown>> {
const agent = this.findAvailableAgent('diagnosis');
if (!agent) return {};
this.updateAgentState(agent.id, 'reasoning');
const diagnosis = await this.hmafSession?.sendTask({
targetAgent: agent.id,
taskType: 'diagnosis_reasoning',
parameters: {
lesions: JSON.stringify(lesions),
knowledgeGraph: 'medical_kg_v2026',
clinicalGuidelines: 'nccn_2026'
},
timeout: 45000
});
this.updateAgentState(agent.id, 'idle');
return diagnosis || {};
}
private async executeReportGeneration(diagnosis: Record<string, unknown>): Promise<string> {
const agent = this.findAvailableAgent('report_gen');
if (!agent) return '';
this.updateAgentState(agent.id, 'generating');
const report = await this.hmafSession?.sendTask({
targetAgent: agent.id,
taskType: 'report_generation',
parameters: {
diagnosis: JSON.stringify(diagnosis),
template: 'structured',
language: 'zh-CN'
},
timeout: 30000
});
this.updateAgentState(agent.id, 'idle');
return report?.content || '';
}
private findAvailableAgent(type: string): AgentInstance | null {
for (const [id, agent] of this.agents.entries()) {
if (agent.type === type && agent.status === 'idle') {
return agent;
}
}
return null;
}
private updateAgentState(agentId: string, state: string): void {
const agent = this.agents.get(agentId);
if (agent) {
agent.status = state as 'idle' | 'acquiring' | 'detecting' | 'reasoning' | 'generating' | 'completed' | 'error';
// 触发全局状态更新,驱动光效变化
AppStorage.setOrCreate('agent_state_update', {
agentId: agentId,
state: state,
timestamp: Date.now()
});
// 根据风险等级更新全局光效
if (state === 'detecting') {
AppStorage.setOrCreate('lesion_risk_change', RiskLevel.MEDIUM);
}
}
}
private formatDiagnosisReport(lesions: Lesion[], diagnosis: Record<string, unknown>, report: string): string {
let result = '## 🏥 智能诊断报告\n\n';
const benignCount = lesions.filter(l => l.riskLevel === RiskLevel.BENIGN).length;
const lowCount = lesions.filter(l => l.riskLevel === RiskLevel.LOW).length;
const mediumCount = lesions.filter(l => l.riskLevel === RiskLevel.MEDIUM).length;
const highCount = lesions.filter(l => l.riskLevel === RiskLevel.HIGH).length;
const malignantCount = lesions.filter(l => l.riskLevel === RiskLevel.MALIGNANT).length;
result += `### 病灶统计\n`;
result += `- 🟢 良性: ${benignCount}\n`;
result += `- 🔵 低危: ${lowCount}\n`;
result += `- 🟡 中危: ${mediumCount}\n`;
result += `- 🟠 高危: ${highCount}\n`;
result += `- 🔴 恶性: ${malignantCount}\n\n`;
result += `### 病灶详情\n`;
for (const lesion of lesions) {
const riskEmoji = lesion.riskLevel === RiskLevel.BENIGN ? '🟢' :
lesion.riskLevel === RiskLevel.LOW ? '🔵' :
lesion.riskLevel === RiskLevel.MEDIUM ? '🟡' :
lesion.riskLevel === RiskLevel.HIGH ? '🟠' : '🔴';
result += `#### ${lesion.id}\n`;
result += `- **风险等级**: ${riskEmoji} ${lesion.riskLevel}\n`;
result += `- **置信度**: ${(lesion.confidence * 100).toFixed(1)}%\n`;
result += `- **类型**: ${lesion.type}\n`;
result += `- **位置**: ${lesion.location}\n`;
result += `- **大小**: ${lesion.size} mm\n\n`;
}
result += `### 诊断结论\n`;
result += `${diagnosis.conclusion || '待进一步检查'}\n\n`;
result += `### 结构化报告\n`;
result += `${report}\n`;
return result;
}
getLesionCount(): Record<RiskLevel, number> {
const counts: Record<RiskLevel, number> = {
[RiskLevel.BENIGN]: 0,
[RiskLevel.LOW]: 0,
[RiskLevel.MEDIUM]: 0,
[RiskLevel.HIGH]: 0,
[RiskLevel.MALIGNANT]: 0
};
for (const lesion of this.lesions) {
counts[lesion.riskLevel]++;
}
return counts;
}
getAgentStatus(): Array<{id: string, type: string, status: string, task: string | null}> {
return Array.from(this.agents.values()).map(agent => ({
id: agent.id,
type: agent.type,
status: agent.status,
task: agent.currentTask?.taskType || null
}));
}
}
5.4 悬浮科室导航(MedicalFloatNavigation.ets)
代码亮点:底部悬浮导航栏不仅承载页面切换功能,更重要的是实时显示诊断任务状态和病灶数量。每个导航项配备风险徽章——良性时绿色光点、低危时蓝色光点、中危时黄色脉冲、高危时橙色脉冲、恶性时红色闪烁。支持长按展开透明度调节面板。
// entry/src/main/ets/components/MedicalFloatNavigation.ets
import { window } from '@kit.ArkUI';
import { RiskLevel, RiskColors } from './LesionLightEffect';
// 导航项配置
interface MedicalNavItem {
id: string;
icon: Resource;
label: string;
page: string;
department?: string;
}
@Component
export struct MedicalFloatNavigation {
@State currentIndex: number = 0;
@State navTransparency: number = 0.70;
@State isExpanded: boolean = false;
@State bottomAvoidHeight: number = 0;
@State agentStates: Map<string, string> = new Map([
['image-acq-1', 'idle'],
['lesion-det-1', 'idle'],
['lesion-det-2', 'idle'],
['diagnosis-1', 'idle'],
['report-gen-1', 'idle']
]);
@State lesionCounts: Record<RiskLevel, number> = {
[RiskLevel.BENIGN]: 0,
[RiskLevel.LOW]: 0,
[RiskLevel.MEDIUM]: 0,
[RiskLevel.HIGH]: 0,
[RiskLevel.MALIGNANT]: 0
};
private navItems: MedicalNavItem[] = [
{ id: 'radiology', icon: $r('app.media.ic_radiology'), label: '影像科', page: 'RadiologyPage', department: 'radiology' },
{ id: 'ct', icon: $r('app.media.ic_ct'), label: 'CT室', page: 'CTPage', department: 'ct' },
{ id: 'mri', icon: $r('app.media.ic_mri'), label: 'MRI室', page: 'MRIPage', department: 'mri' },
{ id: 'ultrasound', icon: $r('app.media.ic_ultrasound'), label: '超声科', page: 'UltrasoundPage', department: 'ultrasound' },
{ id: 'pathology', icon: $r('app.media.ic_pathology'), label: '病理科', page: 'PathologyPage', department: 'pathology' }
];
aboutToAppear(): void {
this.getBottomAvoidArea();
// 监听智能体状态变化
AppStorage.setOrCreate('agent_state_update', (update: {agentId: string, state: string}) => {
this.agentStates.set(update.agentId, update.state);
});
// 监听病灶数量变化
AppStorage.setOrCreate('lesion_count_update', (counts: Record<RiskLevel, number>) => {
this.lesionCounts = counts;
});
}
private async getBottomAvoidArea(): Promise<void> {
try {
const mainWindow = await window.getLastWindow();
const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
this.bottomAvoidHeight = avoidArea.bottomRect.height;
} catch (error) {
console.error('Failed to get avoid area:', error);
}
}
private getAgentStateForNavItem(item: MedicalNavItem): string {
// 根据科室映射到对应智能体
if (item.department === 'radiology') return this.agentStates.get('image-acq-1') || 'idle';
if (item.department === 'ct') return this.agentStates.get('lesion-det-1') || 'idle';
if (item.department === 'mri') return this.agentStates.get('lesion-det-2') || 'idle';
if (item.department === 'ultrasound') return this.agentStates.get('diagnosis-1') || 'idle';
if (item.department === 'pathology') return this.agentStates.get('report-gen-1') || 'idle';
return 'idle';
}
private getTotalLesionCount(): number {
return Object.values(this.lesionCounts).reduce((a, b) => a + b, 0);
}
private getHighestRiskLevel(): RiskLevel {
if (this.lesionCounts[RiskLevel.MALIGNANT] > 0) return RiskLevel.MALIGNANT;
if (this.lesionCounts[RiskLevel.HIGH] > 0) return RiskLevel.HIGH;
if (this.lesionCounts[RiskLevel.MEDIUM] > 0) return RiskLevel.MEDIUM;
if (this.lesionCounts[RiskLevel.LOW] > 0) return RiskLevel.LOW;
return RiskLevel.BENIGN;
}
private getStateBadgeColor(state: string): string {
const colors: Record<string, string> = {
'idle': '#888888',
'acquiring': '#4A90D9',
'detecting': '#F5A623',
'reasoning': '#9B59B6',
'generating': '#2ECC71',
'error': '#FF4444'
};
return colors[state] || '#888888';
}
private getStateBadgeAnimation(state: string): object {
switch (state) {
case 'acquiring':
case 'detecting':
return {
duration: 1500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
case 'reasoning':
case 'generating':
return {
duration: 1000,
curve: Curve.Linear,
iterations: -1
};
case 'error':
return {
duration: 500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
default:
return { duration: 0 };
}
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
// 内容层
Column() {
this.contentBuilder()
}
.padding({ bottom: this.bottomAvoidHeight + 80 })
// 悬浮导航栏
Column() {
Stack() {
// 玻璃拟态背景
Column()
.width('100%')
.height('100%')
.backgroundBlurStyle(BlurStyle.REGULAR)
.opacity(this.navTransparency)
.backdropFilter($r('sys.blur.20'))
// 渐变光效层
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Top,
colors: [
['rgba(255,255,255,0.15)', 0.0],
['rgba(255,255,255,0.05)', 1.0]
]
})
}
.width('100%')
.height('100%')
.borderRadius(24)
.shadow({
radius: 20,
color: 'rgba(0,0,0,0.2)',
offsetX: 0,
offsetY: -4
})
// 导航项
Row() {
ForEach(this.navItems, (item: MedicalNavItem, index: number) => {
Column() {
Stack() {
Image(item.icon)
.width(24)
.height(24)
.fillColor(this.currentIndex === index ? RiskColors[this.getHighestRiskLevel()] : '#666666')
// 病灶数量徽章
if (this.getTotalLesionCount() > 0) {
Column() {
Text(`${this.getTotalLesionCount()}`)
.fontSize(10)
.fontColor('#FFFFFF')
}
.width(18)
.height(18)
.backgroundColor(RiskColors[this.getHighestRiskLevel()])
.borderRadius(9)
.position({ x: 16, y: -8 })
.shadow({
radius: 6,
color: RiskColors[this.getHighestRiskLevel()],
offsetX: 0,
offsetY: 0
})
}
// 智能体状态徽章
if (this.getAgentStateForNavItem(item) !== 'idle') {
Column()
.width(8)
.height(8)
.backgroundColor(this.getStateBadgeColor(this.getAgentStateForNavItem(item)))
.borderRadius(4)
.position({ x: 20, y: -4 })
.shadow({
radius: 4,
color: this.getStateBadgeColor(this.getAgentStateForNavItem(item)),
offsetX: 0,
offsetY: 0
})
.animation(this.getStateBadgeAnimation(this.getAgentStateForNavItem(item)))
}
}
.width(40)
.height(40)
Text(item.label)
.fontSize(11)
.fontColor(this.currentIndex === index ? RiskColors[this.getHighestRiskLevel()] : '#999999')
.margin({ top: 4 })
}
.layoutWeight(1)
.onClick(() => {
this.currentIndex = index;
this.triggerHapticFeedback();
})
})
}
.width('100%')
.height(80)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceAround)
// 透明度调节
if (this.isExpanded) {
Row() {
Text('透明度')
.fontSize(12)
.fontColor('#666666')
.margin({ right: 8 })
Slider({
value: this.navTransparency * 100,
min: 55,
max: 85,
step: 15,
style: SliderStyle.InSet
})
.width(120)
.onChange((value: number) => {
this.navTransparency = value / 100;
})
Text(`${Math.round(this.navTransparency * 100)}%`)
.fontSize(12)
.fontColor('#666666')
.margin({ left: 8 })
}
.width('100%')
.height(40)
.justifyContent(FlexAlign.Center)
.backgroundColor('rgba(255,255,255,0.5)')
.borderRadius({ topLeft: 12, topRight: 12 })
}
}
.width('92%')
.height(this.isExpanded ? 120 : 80)
.margin({ bottom: this.bottomAvoidHeight + 12, left: '4%', right: '4%' })
.animation({
duration: 300,
curve: Curve.Spring,
iterations: 1
})
.gesture(
LongPressGesture({ duration: 500 })
.onAction(() => {
this.isExpanded = !this.isExpanded;
})
)
}
.width('100%')
.height('100%')
}
@BuilderParam contentBuilder: () => void = this.defaultContentBuilder;
@Builder
defaultContentBuilder(): void {
Column() {
Text('内容区域')
.fontSize(16)
.fontColor('#999999')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
private triggerHapticFeedback(): void {
try {
import('@kit.SensorServiceKit').then(sensor => {
sensor.vibrator.startVibration({
type: 'time',
duration: 50
}, { id: 0 });
});
} catch (error) {
console.error('Haptic feedback failed:', error);
}
}
}
5.5 影像视窗与病灶高亮(ImageViewer.ets)
代码亮点:实现具有病灶高亮功能的医学影像视窗。根据诊断结果,在影像上显示病灶标记框,高危病灶边框脉冲闪烁,中危病灶边框柔和呼吸,低危病灶边框轻微变色。
// entry/src/main/ets/components/ImageViewer.ets
import { RiskLevel, RiskColors } from './LesionLightEffect';
interface ImageSlice {
sliceIndex: number;
imageData: ArrayBuffer;
lesions: Array<{id: string, riskLevel: RiskLevel, boundingBox: {x: number, y: number, width: number, height: number}}>;
}
@Component
export struct ImageViewer {
@State currentSlice: number = 0;
@State slices: ImageSlice[] = [];
@State selectedLesion: string = '';
@State zoomLevel: number = 1.0;
@State windowWidth: number = 400;
@State windowCenter: number = 40;
@State currentRiskColor: string = '#2ECC71';
@State currentRiskIntensity: number = 0.4;
aboutToAppear(): void {
// 监听风险等级变化
AppStorage.setOrCreate('current_lesion_color', (color: string) => {
this.currentRiskColor = color;
});
AppStorage.setOrCreate('current_lesion_intensity', (intensity: number) => {
this.currentRiskIntensity = intensity;
});
}
private getLesionBorderColor(lesion: {riskLevel: RiskLevel}): string {
return RiskColors[lesion.riskLevel];
}
private getLesionBorderAnimation(lesion: {riskLevel: RiskLevel}): object {
if (lesion.riskLevel === RiskLevel.MALIGNANT) {
return {
duration: 500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
}
if (lesion.riskLevel === RiskLevel.HIGH) {
return {
duration: 1500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
};
}
return { duration: 0 };
}
build() {
Stack() {
// 影像背景
Column()
.width('100%')
.height('100%')
.backgroundColor('#0a0a0f')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
// DICOM影像渲染
Canvas()
.width('100%')
.height('100%')
.onReady((context: CanvasRenderingContext2D) => {
this.renderDICOM(context);
})
// 病灶标记层
ForEach(this.slices[this.currentSlice]?.lesions || [], (lesion: {id: string, riskLevel: RiskLevel, boundingBox: {x: number, y: number, width: number, height: number}}) => {
Stack() {
// 病灶边框
Column()
.width(lesion.boundingBox.width * this.zoomLevel)
.height(lesion.boundingBox.height * this.zoomLevel)
.backgroundColor('transparent')
.border({
width: lesion.id === this.selectedLesion ? 3 : 2,
color: this.getLesionBorderColor(lesion),
style: BorderStyle.Solid
})
.position({
x: lesion.boundingBox.x * this.zoomLevel,
y: lesion.boundingBox.y * this.zoomLevel
})
.animation(this.getLesionBorderAnimation(lesion))
.onClick(() => {
this.selectedLesion = lesion.id;
AppStorage.setOrCreate('selected_lesion_id', lesion.id);
})
// 病灶标签
if (lesion.id === this.selectedLesion) {
Column() {
Text(`${lesion.riskLevel}`)
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor(RiskColors[lesion.riskLevel])
.borderRadius(4)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
.position({
x: lesion.boundingBox.x * this.zoomLevel,
y: lesion.boundingBox.y * this.zoomLevel - 20
})
}
}
})
// 窗宽窗位调节
Row() {
Column() {
Text('窗宽')
.fontSize(12)
.fontColor('#AAAAAA')
Slider({ value: this.windowWidth, min: 1, max: 4000, step: 10 })
.width(120)
.onChange((value: number) => {
this.windowWidth = value;
})
}
Column() {
Text('窗位')
.fontSize(12)
.fontColor('#AAAAAA')
Slider({ value: this.windowCenter, min: -1000, max: 1000, step: 10 })
.width(120)
.onChange((value: number) => {
this.windowCenter = value;
})
}
}
.position({ x: 20, y: 100 })
// 切片导航
Row() {
Button() {
Text('◀')
.fontSize(20)
.fontColor('#FFFFFF')
}
.type(ButtonType.Circle)
.backgroundColor('rgba(255,255,255,0.2)')
.width(40)
.height(40)
.onClick(() => {
if (this.currentSlice > 0) {
this.currentSlice--;
}
})
Text(`${this.currentSlice + 1} / ${this.slices.length}`)
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ left: 16, right: 16 })
Button() {
Text('▶')
.fontSize(20)
.fontColor('#FFFFFF')
}
.type(ButtonType.Circle)
.backgroundColor('rgba(255,255,255,0.2)')
.width(40)
.height(40)
.margin({ left: 8 })
.onClick(() => {
if (this.currentSlice < this.slices.length - 1) {
this.currentSlice++;
}
})
}
.position({ x: '50%', y: '90%' })
.anchor('50%')
}
.width('100%')
.height('100%')
}
private renderDICOM(context: CanvasRenderingContext2D): void {
// DICOM影像渲染逻辑
const width = context.canvas.width;
const height = context.canvas.height;
// 模拟DICOM渲染
context.fillStyle = '#1a1a2e';
context.fillRect(0, 0, width, height);
// 绘制影像内容
const slice = this.slices[this.currentSlice];
if (slice) {
// 应用窗宽窗位
const minValue = this.windowCenter - this.windowWidth / 2;
const maxValue = this.windowCenter + this.windowWidth / 2;
// 渲染影像数据
context.fillStyle = '#2a2a4e';
context.fillRect(width * 0.1, height * 0.1, width * 0.8, height * 0.8);
}
}
}
5.6 浮动病灶标注面板(LesionPanelWindow.ets)
代码亮点:实现可拖拽的浮动病灶标注面板,支持病灶信息展示、测量工具、3D重建预览。窗口激活时边缘发光增强,失活时自动降低光效强度,通过WindowManager与主窗口光效联动。
// entry/src/main/ets/windows/LesionPanelWindow.ets
import { window } from '@kit.ArkUI';
import { RiskLevel, RiskColors } from '../components/LesionLightEffect';
@Component
export struct LesionPanelWindow {
@State isActive: boolean = false;
@State selectedLesion: {id: string, type: string, riskLevel: RiskLevel, size: number, location: string} | null = null;
@State windowLightColor: string = '#2ECC71';
@State lightIntensity: number = 0.6;
private mainWindow: window.Window | null = null;
aboutToAppear(): void {
this.setupWindow();
// 监听主窗口光效变化
AppStorage.setOrCreate('current_lesion_color', (color: string) => {
this.windowLightColor = color;
});
// 监听选中病灶变化
AppStorage.setOrCreate('selected_lesion_id', (lesionId: string) => {
this.loadLesionDetail(lesionId);
});
}
private async setupWindow(): Promise<void> {
try {
this.mainWindow = await window.getLastWindow();
await this.mainWindow.setWindowBackgroundColor('#00000000');
// 设置窗口为浮动样式
await this.mainWindow.setWindowDecorVisible(false);
await this.mainWindow.resize(450, 700);
// 监听窗口焦点变化
this.mainWindow.on('windowFocusChange', (isFocused: boolean) => {
this.isActive = isFocused;
this.lightIntensity = isFocused ? 0.8 : 0.3;
});
} catch (error) {
console.error('Failed to setup lesion panel window:', error);
}
}
private loadLesionDetail(lesionId: string): void {
// 从全局状态加载病灶详情
const lesionDetail = AppStorage.get<<Record<string, unknown>>(`lesion_${lesionId}`);
if (lesionDetail) {
this.selectedLesion = {
id: lesionId,
type: lesionDetail.type as string,
riskLevel: lesionDetail.riskLevel as RiskLevel,
size: lesionDetail.size as number,
location: lesionDetail.location as string
};
}
}
build() {
Stack() {
// 窗口边框光效
Column()
.width('100%')
.height('100%')
.backgroundColor('transparent')
.border({
width: 2,
color: `${this.windowLightColor}${this.isActive ? 'FF' : '40'}`,
style: BorderStyle.Solid
})
.borderRadius(16)
.animation({
duration: 300,
curve: Curve.EaseInOut
})
// 内容层
Column() {
// 标题栏
Row() {
Text('🔍 病灶详情')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
if (this.selectedLesion) {
Text(this.selectedLesion.riskLevel)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor(RiskColors[this.selectedLesion.riskLevel])
.borderRadius(8)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
// 病灶信息
if (this.selectedLesion) {
Column() {
Text(`病灶ID: ${this.selectedLesion.id}`)
.fontSize(14)
.fontColor('#FFFFFF')
.margin({ bottom: 8 })
Text(`类型: ${this.selectedLesion.type}`)
.fontSize(14)
.fontColor('#AAAAAA')
.margin({ bottom: 8 })
Text(`风险等级: ${this.selectedLesion.riskLevel}`)
.fontSize(14)
.fontColor(RiskColors[this.selectedLesion.riskLevel])
.margin({ bottom: 8 })
Text(`大小: ${this.selectedLesion.size} mm`)
.fontSize(14)
.fontColor('#AAAAAA')
.margin({ bottom: 8 })
Text(`位置: ${this.selectedLesion.location}`)
.fontSize(14)
.fontColor('#AAAAAA')
.margin({ bottom: 8 })
}
.width('100%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.margin({ bottom: 16 })
}
// 测量工具
Column() {
Text('📏 测量工具')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.margin({ bottom: 12 })
Row() {
Button() {
Text('长度')
.fontSize(12)
.fontColor('#FFFFFF')
}
.type(ButtonType.Capsule)
.backgroundColor('rgba(255,255,255,0.2)')
.height(28)
.onClick(() => { /* 长度测量 */ })
Button() {
Text('面积')
.fontSize(12)
.fontColor('#FFFFFF')
}
.type(ButtonType.Capsule)
.backgroundColor('rgba(255,255,255,0.2)')
.height(28)
.margin({ left: 8 })
.onClick(() => { /* 面积测量 */ })
Button() {
Text('体积')
.fontSize(12)
.fontColor('#FFFFFF')
}
.type(ButtonType.Capsule)
.backgroundColor('rgba(255,255,255,0.2)')
.height(28)
.margin({ left: 8 })
.onClick(() => { /* 体积测量 */ })
}
}
.width('100%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.margin({ bottom: 16 })
// 3D重建预览
Column() {
Text('🧊 3D重建预览')
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.margin({ bottom: 12 })
Column()
.width('100%')
.height(150)
.backgroundColor('rgba(0,0,0,0.3)')
.borderRadius(8)
}
.width('100%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
}
.width('100%')
.height('100%')
.padding(2) // 为边框光效留出空间
}
.width('100%')
.height('100%')
.backgroundColor('rgba(15,15,35,0.95)')
.borderRadius(16)
.shadow({
radius: 30,
color: `${this.windowLightColor}${this.isActive ? '40' : '15'}`,
offsetX: 0,
offsetY: 0
})
.animation({
duration: 300,
curve: Curve.EaseInOut
})
}
}
5.7 多窗口光效同步管理器(WindowLightSync.ets)
代码亮点:管理主窗口与浮动病灶标注面板、诊断报告窗口、参考病例窗口之间的光效联动。当病灶风险等级变化时,所有窗口同步切换主题色;当窗口焦点变化时,自动调整光效强度,形成层次分明的视觉体验。
// entry/src/main/ets/managers/WindowLightSync.ets
import { window } from '@kit.ArkUI';
import { RiskLevel, RiskColors } from '../components/LesionLightEffect';
// 窗口类型
export enum WindowType {
MAIN = 'main',
LESION_PANEL = 'lesion_panel',
DIAGNOSIS_REPORT = 'diagnosis_report',
REFERENCE_CASES = 'reference_cases'
}
// 光效主题
export interface LightTheme {
primaryColor: string;
secondaryColor: string;
intensity: number;
pulseSpeed: number;
glowRadius: number;
}
export class WindowLightSync {
private static instance: WindowLightSync;
private windows: Map<<WindowType, window.Window> = new Map();
private currentTheme: LightTheme = {
primaryColor: '#2ECC71',
secondaryColor: '#7EE8A0',
intensity: 0.6,
pulseSpeed: 2000,
glowRadius: 100
};
private focusWindow: WindowType = WindowType.MAIN;
private constructor() {}
static getInstance(): WindowLightSync {
if (!WindowLightSync.instance) {
WindowLightSync.instance = new WindowLightSync();
}
return WindowLightSync.instance;
}
registerWindow(type: WindowType, win: window.Window): void {
this.windows.set(type, win);
// 监听窗口焦点
win.on('windowFocusChange', (isFocused: boolean) => {
if (isFocused) {
this.focusWindow = type;
this.updateFocusLightEffect();
}
});
}
// 同步所有窗口光效
async syncLightEffect(theme: LightTheme): Promise<void> {
this.currentTheme = theme;
for (const [type, win] of this.windows.entries()) {
try {
const isFocused = type === this.focusWindow;
const intensity = isFocused ? theme.intensity : theme.intensity * 0.4;
// 设置窗口背景光效
await win.setWindowBackgroundColor(`#00000000`);
// 同步到AppStorage供各窗口组件读取
AppStorage.setOrCreate(`window_${type}_color`, theme.primaryColor);
AppStorage.setOrCreate(`window_${type}_intensity`, intensity);
AppStorage.setOrCreate(`window_${type}_focused`, isFocused);
} catch (error) {
console.error(`Failed to sync light effect for ${type}:`, error);
}
}
}
// 根据病灶风险等级更新主题
updateThemeByRiskLevel(level: RiskLevel): void {
const themes: Record<RiskLevel, LightTheme> = {
[RiskLevel.BENIGN]: {
primaryColor: RiskColors[RiskLevel.BENIGN],
secondaryColor: '#7EE8A0',
intensity: 0.4,
pulseSpeed: 4000,
glowRadius: 80
},
[RiskLevel.LOW]: {
primaryColor: RiskColors[RiskLevel.LOW],
secondaryColor: '#6BB6FF',
intensity: 0.5,
pulseSpeed: 3000,
glowRadius: 100
},
[RiskLevel.MEDIUM]: {
primaryColor: RiskColors[RiskLevel.MEDIUM],
secondaryColor: '#FFD93D',
intensity: 0.7,
pulseSpeed: 2000,
glowRadius: 120
},
[RiskLevel.HIGH]: {
primaryColor: RiskColors[RiskLevel.HIGH],
secondaryColor: '#FF8888',
intensity: 0.9,
pulseSpeed: 1000,
glowRadius: 150
},
[RiskLevel.MALIGNANT]: {
primaryColor: RiskColors[RiskLevel.MALIGNANT],
secondaryColor: '#FF4444',
intensity: 1.0,
pulseSpeed: 500,
glowRadius: 200
}
};
const theme = themes[level] || themes[RiskLevel.BENIGN];
this.syncLightEffect(theme);
}
private updateFocusLightEffect(): void {
// 重新应用当前主题,根据新的焦点窗口调整强度
this.syncLightEffect(this.currentTheme);
}
// 创建浮动窗口
async createFloatWindow(type: WindowType, width: number, height: number, x: number, y: number): Promise<<window.Window> {
try {
const floatWindow = await window.createWindow({
name: type,
windowType: window.WindowType.TYPE_FLOAT,
ctx: getContext()
});
await floatWindow.moveWindowTo(x, y);
await floatWindow.resize(width, height);
await floatWindow.setWindowBackgroundColor('#00000000');
await floatWindow.showWindow();
this.registerWindow(type, floatWindow);
return floatWindow;
} catch (error) {
console.error(`Failed to create float window ${type}:`, error);
throw error;
}
}
}
5.8 主页面集成(Index.ets)
// entry/src/main/ets/pages/Index.ets
import { ImageViewer } from '../components/ImageViewer';
import { DiagnosisAgentScheduler } from '../services/DiagnosisAgentScheduler';
import { WindowLightSync, WindowType } from '../managers/WindowLightSync';
import { LesionPanelWindow } from '../windows/LesionPanelWindow';
@Entry
@Component
struct Index {
private scheduler: DiagnosisAgentScheduler = DiagnosisAgentScheduler.getInstance();
private lightSync: WindowLightSync = WindowLightSync.getInstance();
aboutToAppear(): void {
this.initializeWindows();
this.initializeScheduler();
}
private async initializeScheduler(): Promise<void> {
await this.scheduler.initialize();
}
private async initializeWindows(): Promise<void> {
try {
const mainWindow = await window.getLastWindow();
this.lightSync.registerWindow(WindowType.MAIN, mainWindow);
// 创建浮动病灶标注面板(PC端特有)
if (deviceInfo.deviceType === '2in1' || deviceInfo.deviceType === 'tablet') {
await this.lightSync.createFloatWindow(
WindowType.LESION_PANEL,
450, 700,
100, 100
);
}
} catch (error) {
console.error('Failed to initialize windows:', error);
}
}
build() {
Stack() {
// 动态环境光背景
Column() {
Column()
.width(400)
.height(400)
.backgroundColor(AppStorage.get<string>('current_lesion_color') || '#2ECC71')
.blur(150)
.opacity(AppStorage.get<number>('current_lesion_intensity') || 0.4)
.position({ x: '50%', y: '30%' })
.anchor('50%')
.animation({
duration: 3000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
}
.width('100%')
.height('100%')
.backgroundColor('#0a0a0f')
// 内容层
Column() {
// 影像视窗
ImageViewer()
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}
六、关键技术总结
6.1 HMAF医疗影像诊断开发清单
| 技术点 | API/方法 | 应用场景 |
|---|---|---|
| 智能体会话创建 | hmaf.createAgentSession({ mode: MULTI_AGENT }) |
多智能体协作诊断 |
| 意图解析 | intents.createIntentEngine({ supportedDomains }) |
医生诊断意图理解 |
| 任务分发 | hmafSession.sendTask({ targetAgent, taskType }) |
智能体间诊断任务调度 |
| 状态监听 | AppStorage全局状态回调 |
跨组件诊断状态同步 |
| 分布式协同 | enableDistributed: true |
多设备诊断协作 |
| 3D UNet模型 | lung_nano_unet |
肺结节检测 |
| 知识图谱推理 | knowledge_graph_reasoning |
诊断推理 |
| NLG报告生成 | structured_report |
报告生成 |
6.2 沉浸光感实现清单
| 技术点 | API/方法 | 应用场景 |
|---|---|---|
| 系统材质效果 | systemMaterialEffect: SystemMaterialEffect.IMMERSIVE |
HdsNavigation标题栏 |
| 背景模糊 | backgroundBlurStyle(BlurStyle.REGULAR) |
悬浮导航玻璃拟态 |
| 背景滤镜 | backdropFilter($r('sys.blur.20')) |
精细模糊控制 |
| 安全区扩展 | expandSafeArea([SafeAreaType.SYSTEM], [...]) |
全屏沉浸布局 |
| 窗口沉浸 | setWindowLayoutFullScreen(true) |
无边框模式 |
| 光效动画 | animation({ duration, iterations: -1 }) |
呼吸灯背景 |
| 动态透明度 | backgroundOpacity |
焦点感知降级 |
6.3 病灶风险光效映射
| 风险等级 | 光效颜色 | 脉冲速度 | 光晕半径 | 视觉语义 |
|---|---|---|---|---|
| 良性 | #2ECC71 | 4000ms | 80px | 柔和绿光,安全放心 |
| 低危 | #4A90D9 | 3000ms | 100px | 平静蓝光,定期随访 |
| 中危 | #F5A623 | 2000ms | 120px | 警示黄光,需要关注 |
| 高危 | #FF6B6B | 1000ms | 150px | 急促橙光,紧急处理 |
| 恶性 | #FF0000 | 500ms | 200px | 警示红光,立即干预 |
6.4 PC端多窗口光效协同
- 主窗口:全屏沉浸,病灶风险光效延伸至所有安全区边缘
- 浮动病灶标注面板窗口:置顶、圆角、阴影,激活时边缘发光增强
- 光效同步:通过
WindowLightSync管理器实现跨窗口主题色联动 - 焦点感知:窗口激活时边缘发光增强,失活时自动降低光效强度
- 风险等级一致性:同一风险等级在所有窗口中保持相同色彩标识
七、调试与适配建议
- HMAF会话管理:多智能体并发时注意会话资源释放,避免内存泄漏
- 光效功耗优化:夜间模式降低光效刷新率,延长OLED屏幕寿命
- 意图识别校准:不同影像模态需训练专用意图模型,提升解析准确率
- 分布式设备测试:测试跨设备诊断协作时的网络延迟与状态同步
- 无障碍支持:为视障医生提供语音播报诊断结果,光效切换时播放提示音
- 隐私保护:患者影像数据本地处理,仅上传脱敏后的诊断日志
- 医疗合规:遵循HIPAA/GDPR等医疗数据保护法规,确保数据安全
八、总结与展望
本文基于HarmonyOS 6(API 23)的悬浮导航、沉浸光感与HMAF智能体框架特性,完整实战了一款面向PC端的"医智助手"医疗影像AI智能体辅助诊断平台。核心创新点总结:
-
HMAF四层诊疗架构:基于Agent Framework Kit构建"影像采集-病灶检测-诊断推理-报告生成"四层智能体协作体系,实现影像采集→病灶检测→诊断推理→报告生成的完整诊疗闭环
-
病灶风险光效系统:每个风险等级拥有专属光效标识(良性绿、低危蓝、中危黄、高危橙、恶性红),根据诊断结果动态切换脉冲节奏与光晕强度,实现"风险直觉感知"
-
悬浮科室导航:底部悬浮页签不仅承载页面切换,更实时显示诊断任务状态徽章(采集中脉冲、检测中旋转、推理中呼吸)和病灶数量角标,玻璃拟态设计+三档透明度调节
-
PC级多窗口协作诊疗:主影像视窗 + 浮动病灶标注面板 + 浮动诊断报告窗口 + 浮动参考病例窗口的四层架构,通过
WindowLightSync实现跨窗口光效联动与焦点感知 -
DICOM影像可视化:基于
Canvas实现DICOM影像渲染、窗宽窗位调节、病灶标记框高亮、切片导航,支持病灶交互和3D重建预览
未来扩展方向:
- 接入分布式软总线4.0,实现PC主控+平板阅片+手机随访的多设备协作
- AI智能体诊断市场:支持第三方开发者发布自定义诊断智能体,通过光效标识区分专科能力
- 多模态融合诊断:整合CT、MRI、超声、病理等多模态数据,实现跨模态智能诊断
- 联邦学习优化:隐私保护下的跨医院智能优化,共享模型参数不出域
- 手术导航集成:结合AR技术,实现影像引导的精准手术导航
转载自:https://blog.csdn.net/u014727709/article/details/161192790
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐


所有评论(0)