⌛ 本弹窗10秒后关闭。
04_卷四_应用篇

卷四:应用篇——推理系统设计与用户界面开发

🏗️ 章节导航

  • [[#7.1 推理引擎架构]]
  • [[#7.2 自动模型选择]]
  • [[#7.3 结果解释机制]]
  • [[#7.4 错误处理策略]]
  • [[#8.1 Tkinter基础]]
  • [[#8.2 界面设计]]
  • [[#8.3 用户交互流程]]
  • [[#8.4 报告生成功能]]

📚 本章导读

本章节深入讲解多模态抑郁检测系统的应用层实现,包括推理系统设计和用户界面开发。通过本章学习,您将:

  • ✅ 掌握推理引擎的架构设计和工作原理
  • ✅ 理解自动模型选择和决策融合机制
  • ✅ 学会Tkinter GUI界面开发
  • ✅ 掌握报告生成和用户交互设计

7.1 推理引擎架构

7.1.1 推理引擎设计

推理引擎是系统的核心组件,负责加载模型、处理特征、执行预测并生成结果。其架构设计如下:

class DepressionInference:
    """抑郁检测推理引擎"""
    
    def __init__(self):
        """初始化推理引擎"""
        self.models = {}          # 分类模型字典
        self.score_models = {}    # 回归模型字典(用于严重程度评估)
        self.schema = {}          # 特征列架构
        self.thresholds = {}      # 判定阈值
        self.load_resources()     # 加载所有资源
    
    def load_resources(self):
        """加载模型、特征架构和阈值配置"""
        # 1. 加载特征架构
        # 2. 加载阈值配置
        # 3. 加载分类和回归模型

核心组件

  • 模型管理:支持多模态模型(音频、视频、多模态融合)
  • 特征处理:根据特征架构构建特征向量
  • 阈值管理:支持手动和自动阈值配置
  • 决策融合:多模态决策融合策略

设计原则

  • 模块化:清晰的职责划分
  • 灵活性:支持不同模态的输入
  • 可扩展性:易于添加新的模型和特征
  • 鲁棒性:完善的错误处理和边界情况处理

7.1.2 模型加载机制

模型加载是推理引擎的重要功能,需要处理多种模型文件:

def load_models(self):
    """加载所有模型文件"""
    model_dir = config_local.MODEL_DIR
    
    # 分类模型路径配置
    classification_models = {
        'audio': "depression_audio_model.pkl",
        'visual': "depression_visual_model.pkl", 
        'multimodal': "depression_high_quality_model.pkl"
    }
    
    # 回归模型路径配置(用于严重程度评估)
    regression_models = {
        'audio': "depression_audio_score_model.pkl",
        'visual': "depression_visual_score_model.pkl",
        'multimodal': "depression_score_model.pkl"
    }
    
    # 加载分类模型
    for model_type, filename in classification_models.items():
        model_path = os.path.join(model_dir, filename)
        if os.path.exists(model_path):
            self.models[model_type] = joblib.load(model_path)
    
    # 加载回归模型
    for model_type, filename in regression_models.items():
        model_path = os.path.join(model_dir, filename)
        if os.path.exists(model_path):
            self.score_models[model_type] = joblib.load(model_path)

模型加载策略

  • 分类模型:用于抑郁/非抑郁二分类
  • 回归模型:用于预测PHQ-8分数,评估严重程度
  • 懒加载:只在需要时加载模型,节省内存
  • 容错处理:模型文件不存在时优雅降级

文件格式

  • 使用joblib序列化格式,高效加载大型模型
  • 支持scikit-learn Pipeline对象的完整保存和加载

7.1.3 特征处理流程

特征处理是连接特征提取和模型推理的关键环节:

def prepare_features(self, combined_features, mode):
    """
    准备模型输入特征
    
    参数:
        combined_features: 包含所有特征的字典
        mode: 模型类型 ('audio', 'visual', 'multimodal')
    
    返回:
        numpy数组: 模型输入特征向量
    """
    # 根据模型类型获取特征列
    if mode == 'multimodal':
        feature_cols = self.schema.get('multimodal_feature_cols', [])
        if not feature_cols:
            # 回退策略:拼接音频和视频特征
            audio_cols = [k for k in combined_features.keys() if k.startswith('aud_')]
            visual_cols = [k for k in combined_features.keys() if k.startswith('vis_')]
            feature_cols = audio_cols + visual_cols
    else:
        feature_cols_key = f"{mode}_feature_cols"
        feature_cols = self.schema.get(feature_cols_key, [])
        if not feature_cols:
            # 回退策略:根据前缀筛选特征
            prefix = 'aud_' if mode == 'audio' else 'vis_'
            feature_cols = [k for k in combined_features.keys() if k.startswith(prefix)]
    
    # 构建特征向量
    feature_vector = []
    for col in feature_cols:
        feature_value = combined_features.get(col, 0.0)
        feature_vector.append(float(feature_value))
    
    return np.array(feature_vector).reshape(1, -1)

特征处理步骤

  1. 特征列选择:根据模型类型选择对应的特征列
  2. 回退机制:特征架构缺失时的备选方案
  3. 特征构建:将字典形式的特征转换为模型所需的numpy数组
  4. 维度调整:确保特征向量维度正确

设计考虑

  • 特征对齐:确保推理时的特征顺序与训练时一致
  • 缺失处理:为缺失特征提供默认值
  • 类型转换:确保所有特征值为浮点数
  • 形状调整:reshape为(1, -1)以符合scikit-learn模型输入要求

7.2 自动模型选择

7.2.1 模态检测算法

系统能够自动检测输入数据的模态类型,并选择合适的模型:

def detect_modality(self, combined_features):
    """
    检测可用的模态类型
    
    参数:
        combined_features: 提取的特征字典
    
    返回:
        str: 最佳模型类型 ('multimodal', 'audio', 'visual', 'none')
    """
    # 检查特征前缀判断模态
    has_audio = any(k.startswith('aud_') for k in combined_features)
    has_visual = any(k.startswith('vis_') for k in combined_features)
    
    # 音频有效性验证
    if has_audio:
        f0_mean = combined_features.get('aud_f0_mean', 0)
        # 基频过低可能表示无效音频
        if f0_mean < 40:
            has_audio = False
    
    # 模型可用性检查
    has_audio_model = 'audio' in self.models
    has_visual_model = 'visual' in self.models
    has_multimodal_model = 'multimodal' in self.models
    
    # 自动模型选择逻辑
    if has_audio and has_visual:
        # 多模态输入,优先使用融合策略
        return 'multimodal_fusion'
    elif has_audio and has_audio_model:
        # 仅音频输入
        return 'audio'
    elif has_visual and has_visual_model:
        # 仅视频输入
        return 'visual'
    else:
        # 无可用模型或特征不足
        return 'none'

模态检测策略

  • 特征前缀检测:通过特征键名前缀判断模态
  • 有效性验证:验证音频特征的合理性
  • 模型可用性检查:确认对应模型是否已加载
  • 优先级排序:多模态 > 音频 > 视频

特殊处理

  • 音频有效性:通过基频(F0)判断音频是否有效
  • 模型缺失:提供优雅的降级机制
  • 特征不足:处理特征数量不足的情况

7.2.2 模型切换逻辑

系统支持根据输入数据自动切换不同的模型:

def predict(self, combined_features):
    """
    核心推理逻辑,支持自动模型切换
    
    参数:
        combined_features: 提取的特征字典
    
    返回:
        dict: 包含预测结果的字典
    """
    # 1. 检测模态类型
    mode = self.detect_modality(combined_features)
    
    if mode == 'none':
        return {
            'error': '无可用模型或特征不足',
            'proba': 0,
            'threshold': 0,
            'severity': '无法评估'
        }
    
    # 2. 根据模态类型执行推理
    if mode == 'multimodal_fusion':
        # 多模态融合推理
        result = self._predict_multimodal_fusion(combined_features)
    else:
        # 单模态推理
        result = self._predict_single_modality(combined_features, mode)
    
    return result

模型切换策略

  • 自动检测:根据输入特征自动选择最佳模型
  • 无缝切换:用户无需手动选择模型类型
  • 降级机制:多模态模型不可用时自动切换到单模态
  • 统一接口:提供一致的推理接口,简化调用

优势

  • 用户友好:无需用户了解技术细节
  • 灵活适应:支持不同类型的输入数据
  • 鲁棒性强:单个模型失败不影响整体系统
  • 性能优化:选择最适合当前输入的模型

7.2.3 加权决策融合

多模态决策融合是系统的核心创新点,采用加权投票策略:

def _predict_multimodal_fusion(self, combined_features):
    """
    多模态决策融合推理
    
    参数:
        combined_features: 包含音频和视频特征的字典
    
    返回:
        dict: 融合后的预测结果
    """
    # 音频模型推理
    audio_features = self.prepare_features(combined_features, 'audio')
    audio_proba = float(self.models['audio'].predict_proba(audio_features)[0][1])
    
    # 视频模型推理
    visual_features = self.prepare_features(combined_features, 'visual')
    visual_proba = float(self.models['visual'].predict_proba(visual_features)[0][1])
    
    # 加权融合(基于实验确定的最佳权重)
    # 音频权重:0.55,视频权重:0.45
    fused_proba = audio_proba * 0.55 + visual_proba * 0.45
    
    # 获取多模态阈值
    threshold, threshold_source = self.get_threshold('multimodal')
    
    # 判定结果
    is_depressed = fused_proba >= threshold
    
    # 构建融合特征用于严重程度评估
    if 'multimodal_feature_cols' in self.schema:
        fusion_features = self.prepare_features(combined_features, 'multimodal')
    else:
        # 特征拼接:音频特征在前,视频特征在后
        fusion_features = np.hstack((audio_features, visual_features))
    
    # 严重程度评估
    severity_result = self._evaluate_severity(fusion_features, 'multimodal')
    
    return {
        'mode': 'multimodal',
        'proba': fused_proba,
        'threshold': threshold,
        'threshold_source': threshold_source,
        'is_depressed': is_depressed,
        **severity_result
    }

融合策略

  • 加权平均:基于实验确定的最优权重
  • 权重依据:音频0.55,视频0.45(根据模型性能确定)
  • 特征拼接:为回归模型准备融合特征
  • 阈值统一:使用多模态专用阈值

融合优势

  • 互补性:音频和视频特征相互补充
  • 鲁棒性:单个模态质量不佳时仍能获得较好结果
  • 准确性:融合结果通常优于单模态
  • 可解释性:可以分析每个模态的贡献

7.3 结果解释机制

7.3.1 特征贡献度分析

系统提供特征贡献度分析,帮助理解模型决策依据:

def analyze_feature_contribution(self, features, model_type):
    """
    分析特征对预测结果的贡献度
    
    参数:
        features: 输入特征向量
        model_type: 模型类型
    
    返回:
        list: 特征贡献度列表
    """
    if model_type not in self.models:
        return []
    
    model = self.models[model_type]
    
    # 获取模型系数(仅适用于线性模型)
    if hasattr(model, 'coef_'):
        coefficients = model.coef_[0]
        
        # 获取特征列名
        if model_type == 'multimodal':
            feature_names = self.schema.get('multimodal_feature_cols', [])
        else:
            feature_names = self.schema.get(f'{model_type}_feature_cols', [])
        
        # 计算每个特征的贡献度
        contributions = []
        for i, (feature_name, coef, feature_value) in enumerate(zip(
            feature_names, coefficients, features[0]
        )):
            contribution = coef * feature_value
            contributions.append({
                'feature': feature_name,
                'value': feature_value,
                'coefficient': coef,
                'contribution': contribution
            })
        
        # 按贡献度绝对值排序
        contributions.sort(key=lambda x: abs(x['contribution']), reverse=True)
        return contributions
    
    return []

贡献度计算

  • 线性模型:使用系数乘以特征值计算贡献
  • 排序展示:按贡献度绝对值降序排列
  • 正负贡献:区分正向和负向贡献
  • 可解释性:帮助理解哪些特征影响了预测结果

应用场景

  • 临床辅助:帮助医生理解AI决策依据
  • 模型调试:识别异常特征影响
  • 用户解释:向用户解释预测结果
  • 特征优化:指导特征工程改进

7.3.2 风险因素识别

系统能够识别关键风险因素,提供针对性的分析:

def identify_risk_factors(self, features, contributions):
    """
    识别关键风险因素
    
    参数:
        features: 输入特征
        contributions: 特征贡献度列表
    
    返回:
        dict: 风险因素分析结果
    """
    risk_factors = {
        'audio_risk_factors': [],
        'visual_risk_factors': [],
        'summary': ''
    }
    
    # 音频风险因素
    audio_contributions = [c for c in contributions if c['feature'].startswith('aud_')]
    
    # 检查语音特征风险
    if 'aud_f0_mean' in features:
        f0_mean = features['aud_f0_mean']
        if f0_mean < 100:  # 基频过低
            risk_factors['audio_risk_factors'].append('语音基频过低,可能表现为情绪低落')
    
    if 'aud_voiced_ratio' in features:
        voiced_ratio = features['aud_voiced_ratio']
        if voiced_ratio < 0.6:  # 语音比例过低
            risk_factors['audio_risk_factors'].append('语音比例低,可能存在言语减少')
    
    # 视频风险因素
    visual_contributions = [c for c in contributions if c['feature'].startswith('vis_')]
    
    # 检查面部表情特征
    if 'vis_AU12_r_mean' in features:  # 微笑强度
        smile_intensity = features['vis_AU12_r_mean']
        if smile_intensity < 0.5:
            risk_factors['visual_risk_factors'].append('微笑表情少,可能情绪低落')
    
    if 'vis_AU04_r_mean' in features:  # 眉毛降低(悲伤)
        brow_lower = features['vis_AU04_r_mean']
        if brow_lower > 2.0:
            risk_factors['visual_risk_factors'].append('频繁皱眉,可能存在悲伤情绪')
    
    # 生成总结
    all_factors = risk_factors['audio_risk_factors'] + risk_factors['visual_risk_factors']
    if all_factors:
        risk_factors['summary'] = '、'.join(all_factors)
    else:
        risk_factors['summary'] = '未发现明显风险因素'
    
    return risk_factors

风险因素类型

  • 音频特征:基频、语音比例、语速等
  • 视频特征:面部表情、头部动作、眼神等
  • 综合分析:多模态特征的综合评估

识别方法

  • 阈值判断:基于正常范围的偏离程度
  • 贡献度分析:结合特征贡献度
  • 模式识别:识别典型的抑郁表现模式
  • 临床经验:结合临床心理学知识

7.3.3 结果可视化

系统提供直观的结果可视化,便于用户理解:

def generate_visualization(self, result):
    """
    生成结果可视化数据
    
    参数:
        result: 推理结果字典
    
    返回:
        dict: 可视化数据
    """
    visualization = {
        'gauge_data': {
            'value': result['proba'] * 100,
            'threshold': result['threshold'] * 100,
            'severity': result['severity'],
            'severity_color': result['severity_color']
        },
        'risk_factors': {
            'audio': [],
            'visual': []
        },
        'feature_importance': []
    }
    
    # 风险因素可视化
    if 'risk_factors' in result:
        visualization['risk_factors'] = result['risk_factors']
    
    # 特征重要性可视化
    if 'feature_contributions' in result:
        top_features = result['feature_contributions'][:5]  # 取前5个最重要特征
        visualization['feature_importance'] = [
            {
                'name': feat['feature'].replace('aud_', '音频_').replace('vis_', '视频_'),
                'contribution': feat['contribution'],
                'value': feat['value']
            }
            for feat in top_features
        ]
    
    return visualization

可视化元素

  • 仪表盘:直观显示抑郁概率和严重程度
  • 风险因素列表:突出显示关键风险因素
  • 特征重要性:展示影响预测的重要特征
  • 严重程度指示器:不同颜色标识不同严重程度

可视化策略

  • 色彩编码:使用红黄绿表示严重程度
  • 简化标签:将技术特征名转换为易懂的描述
  • 重点突出:强调最重要的风险因素
  • 层次展示:按重要性顺序展示信息

7.4 错误处理策略

7.4.1 异常捕获机制

系统实现了全面的异常捕获机制,确保系统稳定性:

def predict_safe(self, features):
    """
    安全的预测方法,包含完整的异常处理
    
    参数:
        features: 输入特征字典
    
    返回:
        dict: 预测结果或错误信息
    """
    try:
        # 验证输入特征
        if not isinstance(features, dict) or not features:
            return {
                'error': '输入特征无效',
                'proba': 0,
                'severity': '无法评估'
            }
        
        # 执行预测
        result = self.predict(features)
        
        # 验证输出结果
        if not isinstance(result, dict) or 'error' in result:
            return {
                'error': result.get('error', '预测失败'),
                'proba': 0,
                'severity': '无法评估'
            }
        
        return result
        
    except KeyError as e:
        return {
            'error': f'特征缺失: {e}',
            'proba': 0,
            'severity': '无法评估'
        }
    except ValueError as e:
        return {
            'error': f'特征值错误: {e}',
            'proba': 0,
            'severity': '无法评估'
        }
    except Exception as e:
        return {
            'error': f'预测过程出错: {str(e)}',
            'proba': 0,
            'severity': '无法评估'
        }

异常类型处理

  • 输入验证异常:检查输入特征的有效性
  • 特征缺失异常:处理缺少关键特征的情况
  • 值错误异常:处理特征值类型或范围错误
  • 通用异常:捕获其他未预期的异常

错误处理原则

  • 尽早验证:在处理前验证输入
  • 明确错误信息:提供具体的错误原因
  • 优雅降级:错误时返回合理的默认值
  • 日志记录:记录详细的错误信息

7.4.2 错误信息反馈

系统提供友好的错误信息反馈机制:

def format_error_message(self, error_type, details=None):
    """
    格式化错误信息
    
    参数:
        error_type: 错误类型
        details: 错误详情
    
    返回:
        str: 格式化的错误信息
    """
    error_templates = {
        'feature_missing': '缺少必要的特征数据',
        'model_not_found': '模型文件未找到,请检查模型路径',
        'invalid_input': '输入数据无效,请检查文件格式',
        'processing_error': '数据处理失败',
        'prediction_error': '预测过程出错',
        'unknown_error': '发生未知错误'
    }
    
    message = error_templates.get(error_type, error_templates['unknown_error'])
    
    if details:
        message += f":{details}"
    
    return message

错误分类

  • 特征错误:特征缺失、无效或格式错误
  • 模型错误:模型文件不存在或加载失败
  • 处理错误:数据处理过程中的错误
  • 预测错误:模型预测过程中的错误

用户体验考虑

  • 友好语言:使用用户易懂的语言描述错误
  • 具体指导:提供解决问题的建议
  • 分层反馈:根据错误严重程度提供不同级别的反馈
  • 日志记录:详细记录错误信息便于调试

7.4.3 系统稳定性保障

系统通过多重机制保障稳定性:

def ensure_system_stability(self):
    """
    确保系统稳定性的检查和修复
    
    返回:
        bool: 系统是否稳定
    """
    issues = []
    
    # 1. 检查模型文件
    for model_type, model in self.models.items():
        if model is None:
            issues.append(f"{model_type}模型未加载")
    
    # 2. 检查特征架构
    if not self.schema:
        issues.append("特征架构未加载")
    
    # 3. 检查阈值配置
    if not self.thresholds:
        issues.append("阈值配置未加载")
    
    # 4. 检查输出目录
    output_dir = config_local.REPORT_SAVE_DIR
    if not os.path.exists(output_dir):
        try:
            os.makedirs(output_dir)
        except Exception as e:
            issues.append(f"无法创建输出目录: {e}")
    
    # 5. 检查外部依赖
    if not self._check_external_dependencies():
        issues.append("外部依赖检查失败")
    
    return len(issues) == 0

稳定性保障措施

  • 模型完整性检查:确保所有必要模型已加载
  • 配置验证:验证配置文件的完整性
  • 资源检查:检查文件系统权限和空间
  • 依赖验证:检查外部工具和库的可用性
  • 自动修复:尝试自动修复一些常见问题

恢复机制

  • 模型重新加载:模型加载失败时自动重试
  • 默认配置:配置缺失时使用默认值
  • 降级服务:部分功能不可用时提供基础服务
  • 异常隔离:单个模块故障不影响整体系统

8.1 Tkinter基础

8.1.1 GUI框架介绍

Tkinter是Python标准库中的GUI框架,具有以下特点:

  • 跨平台:支持Windows、macOS、Linux
  • 简单易用:API简洁,学习曲线平缓
  • 内置组件:提供丰富的UI组件
  • 事件驱动:基于事件的编程模型
import tkinter as tk
from tkinter import ttk

# 创建主窗口
root = tk.Tk()
root.title("多模态抑郁风险评估系统")
root.geometry("900x700")

# 设置窗口图标
try:
    root.iconbitmap("favicon.ico")
except Exception as e:
    print(f"图标加载失败: {e}")

# 运行主循环
root.mainloop()

核心组件

  • Tk:主窗口类
  • Frame:框架容器
  • Label:文本标签
  • Button:按钮
  • Entry:输入框
  • Text:文本区域
  • Progressbar:进度条
  • Notebook:选项卡

布局管理器

  • pack:简单的垂直/水平布局
  • grid:网格布局,精确控制位置
  • place:绝对位置布局

8.1.2 窗口与组件

系统使用了多种Tkinter组件构建用户界面:

class DepressionTestApp:
    def __init__(self, root):
        self.root = root
        self.root.title("多模态抑郁风险评估系统")
        self.root.geometry("900x700")
        
        # 初始化推理引擎
        self.predictor = inference.DepressionInference()
        
        # 任务控制
        self.cancel_event = threading.Event()
        
        # 样式配置
        self.setup_styles()
        
        # 构建界面
        self.create_widgets()

主要组件设计

  • 标题区域:显示系统名称和图标
  • 用户信息区域:收集用户基本信息
  • 文件选择区域:选择音视频文件
  • 控制按钮区域:开始/停止评估、设置等
  • 进度显示区域:显示处理进度
  • 日志输出区域:显示运行日志和结果
  • 设置窗口:配置系统参数

组件特性

  • 样式统一:使用ttk.Style统一组件样式
  • 响应式布局:适应不同窗口大小
  • 事件绑定:按钮点击、文件选择等事件
  • 线程安全:UI更新使用after机制

8.1.3 布局管理器

系统采用多种布局管理器组合使用:

def create_widgets(self):
    # 主容器 - 使用pack布局
    main_frame = ttk.Frame(self.root, style="Main.TFrame", padding="20")
    main_frame.pack(fill=tk.BOTH, expand=True)
    
    # 标题 - 使用pack布局
    ttk.Label(main_frame, text="🧠 多模态抑郁风险评估系统", style="Title.TLabel").pack(pady=(0, 20))
    
    # 用户信息区域 - 使用grid布局
    user_frame = ttk.LabelFrame(main_frame, text="个人信息", padding="15")
    user_frame.pack(fill=tk.X, pady=10)
    
    grid_opts = {'padx': 10, 'pady': 5, 'sticky': 'w'}
    ttk.Label(user_frame, text="姓名:").grid(row=0, column=0, **grid_opts)
    self.name_var = tk.StringVar()
    ttk.Entry(user_frame, textvariable=self.name_var, width=20).grid(row=0, column=1, **grid_opts)
    
    # 文件选择区域 - 使用pack布局
    file_frame = ttk.LabelFrame(main_frame, text="数据输入", padding="15")
    file_frame.pack(fill=tk.X, pady=10)
    
    self.file_path_var = tk.StringVar()
    ttk.Entry(file_frame, textvariable=self.file_path_var, width=60).pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
    ttk.Button(file_frame, text="📂 选择音视频文件", command=self.browse_file).pack(side=tk.LEFT)

布局策略

  • 主容器:使用pack布局,填充整个窗口
  • 区域划分:功能相关组件放在同一个Frame中
  • 网格布局:用户信息使用grid精确对齐
  • 混合布局:根据组件特性选择合适的布局管理器

布局优化

  • 响应式设计:使用fill和expand参数
  • 间距控制:合理设置padx和pady
  • 对齐方式:使用sticky参数控制组件对齐
  • 空间分配:使用weight参数控制空间分配

8.2 界面设计

8.2.1 主界面布局

系统主界面采用清晰的模块化设计:

def setup_styles(self):
    """配置界面样式"""
    style = ttk.Style()
    
    # 使用Windows原生风格
    try:
        style.theme_use('vista')
    except:
        style.theme_use('alt')
    
    # 定义颜色方案
    COLORS = {
        'bg': "#f5f6fa",             # 浅灰白背景
        'text': "#2c3e50",           # 深色文本
        'accent': "#3498db",         # 蓝色强调
        'card_bg': "white"           # 白色卡片
    }
    
    # 配置组件样式
    style.configure("Title.TLabel", 
                   font=("Microsoft YaHei", 24, "bold"), 
                   background=COLORS['bg'], 
                   foreground=COLORS['text'])
    
    style.configure("Header.TLabel", 
                   font=("Microsoft YaHei", 14, "bold"), 
                   background=COLORS['bg'], 
                   foreground=COLORS['accent'])
    
    style.configure("Card.TFrame", 
                   background="white", 
                   relief="solid", 
                   borderwidth=1)

设计原则

  • 简洁现代:采用现代简约风格
  • 色彩协调:使用柔和的配色方案
  • 层次分明:通过阴影和边框创建视觉层次
  • 易读性:使用清晰的字体和合适的字号

界面元素

  • 标题:使用大号粗体字体,突出显示
  • 卡片设计:使用白色背景和阴影效果
  • 按钮样式:统一的按钮样式,区分不同功能
  • 颜色编码:使用不同颜色表示不同状态

8.2.2 用户信息输入

用户信息收集界面设计:

# 用户信息区域
user_frame = ttk.LabelFrame(main_frame, text="个人信息", padding="15")
user_frame.pack(fill=tk.X, pady=10)

grid_opts = {'padx': 10, 'pady': 5, 'sticky': 'w'}

# 姓名字段
ttk.Label(user_frame, text="姓名:").grid(row=0, column=0, **grid_opts)
self.name_var = tk.StringVar()
ttk.Entry(user_frame, textvariable=self.name_var, width=20).grid(row=0, column=1, **grid_opts)

# 性别字段
ttk.Label(user_frame, text="性别:").grid(row=0, column=2, **grid_opts)
self.gender_var = tk.StringVar(value="男")
ttk.Combobox(user_frame, 
             textvariable=self.gender_var, 
             values=["男", "女"], 
             width=10, 
             state="readonly").grid(row=0, column=3, **grid_opts)

# 年龄字段
ttk.Label(user_frame, text="年龄:").grid(row=0, column=4, **grid_opts)
self.age_var = tk.StringVar()
ttk.Entry(user_frame, textvariable=self.age_var, width=10).grid(row=0, column=5, **grid_opts)

设计特点

  • 标签清晰:明确的字段标签
  • 输入验证:年龄字段可以添加验证
  • 默认值:性别字段提供默认值
  • 网格布局:整齐的网格排列

数据收集

  • 姓名:用于报告生成
  • 性别:可能影响某些特征的解释
  • 年龄:用于参考,不直接用于预测

8.2.3 文件选择功能

文件选择界面设计:

# 文件选择区域
file_frame = ttk.LabelFrame(main_frame, text="数据输入", padding="15")
file_frame.pack(fill=tk.X, pady=10)

self.file_path_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.file_path_var, width=60).pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
ttk.Button(file_frame, text="📂 选择音视频文件", command=self.browse_file, style="Action.TButton").pack(side=tk.LEFT)

def browse_file(self):
    """浏览并选择音视频文件"""
    file_path = filedialog.askopenfilename(
        filetypes=[
            ("Media Files", "*.mp4 *.avi *.wav *.mp3 *.mkv"), 
            ("All Files", "*.*")
        ]
    )
    if file_path:
        self.file_path_var.set(file_path)

文件类型支持

  • 视频文件:.mp4, .avi, .mkv, .mov
  • 音频文件:.wav, .mp3
  • 其他文件:通过"所有文件"选项支持

用户体验

  • 文件过滤器:只显示支持的文件类型
  • 路径显示:在输入框中显示选中的文件路径
  • 按钮图标:使用📂图标增强可视化效果
  • 错误处理:处理文件选择取消的情况

8.3 用户交互流程

8.3.1 任务执行线程

系统使用多线程处理耗时任务,保持UI响应性:

def start_analysis(self):
    """开始分析任务"""
    file_path = self.file_path_var.get()
    if not file_path or not os.path.exists(file_path):
        messagebox.showerror("错误", "请选择有效的文件路径")
        return
        
    # 更新UI状态
    self.btn_action.config(text="⏹️ 停止评估", style="Danger.TButton")
    self.progress['value'] = 0
    self.cancel_event.clear()
    
    # 清空日志
    self.log_text.config(state="normal")
    self.log_text.delete('1.0', tk.END)
    self.log_text.config(state="disabled")
    
    self.log("正在启动分析任务...")
    
    # 在新线程中执行任务
    threading.Thread(target=self.run_task, args=(file_path,), daemon=True).start()

def run_task(self, file_path):
    """在后台线程中执行分析任务"""
    try:
        # 进度更新回调
        def update_progress(val):
            self.root.after(0, lambda: self.progress.configure(value=val))
        
        update_progress(5)
        
        # 1. 特征提取
        is_video = file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))
        
        visual_features = {}
        if is_video:
            visual_features = feature_extractor.extract_visual_features(
                file_path, self.log, update_progress, self.cancel_event
            )
        else:
            self.log("⚠️ 输入为音频文件,跳过视觉提取")
            update_progress(40)
        
        # 检查取消标志
        if self.cancel_event.is_set():
            self.log("⚠️ 任务已取消")
            return
        
        # 提取音频特征
        audio_features = feature_extractor.extract_audio_features(
            file_path, self.log, update_progress, self.cancel_event
        )
        
        # 检查取消标志
        if self.cancel_event.is_set():
            self.log("⚠️ 任务已取消")
            return
        
        # 合并特征
        combined_features = {**visual_features, **audio_features}
        
        # 2. 推理
        self.log("🧠 正在进行模型推理...")
        update_progress(95)
        result = self.predictor.predict(combined_features)
        
        # 3. 结果处理和报告生成
        # ...
        
    except Exception as e:
        self.log(f"❌ 发生未预期的错误: {e}")
        import traceback
        print(traceback.format_exc())
    finally:
        # 在主线程中重置UI状态
        self.root.after(0, lambda: self.reset_ui_state())

线程设计

  • 后台线程:使用daemon线程执行耗时任务
  • UI更新:使用after机制在主线程更新UI
  • 取消机制:使用threading.Event实现任务取消
  • 异常处理:捕获并记录所有异常

任务流程

  1. 特征提取:提取音频和视频特征
  2. 模型推理:使用推理引擎进行预测
  3. 结果处理:生成可视化结果
  4. 报告生成:创建评估报告
  5. UI更新:显示结果和更新状态

8.3.2 进度更新机制

系统实现了实时进度更新机制:

def log(self, msg, level="info"):
    """
    带颜色和时间戳的日志输出
    
    参数:
        msg: 日志消息
        level: 日志级别 (info, success, warning, error, highlight)
    """
    # 自动推断日志级别
    if "⚠️" in msg: level = "warning"
    elif "❌" in msg or "🛑" in msg: level = "error"
    elif "✅" in msg: level = "success"
    elif "🧠" in msg or "🚀" in msg: level = "highlight"
    
    def _log():
        self.log_text.config(state="normal")
        timestamp = datetime.datetime.now().strftime('%H:%M:%S')
        
        # 插入时间戳
        self.log_text.insert(tk.END, f"[{timestamp}] ", "timestamp")
        # 插入消息
        self.log_text.insert(tk.END, f"{msg}\n", level)
        
        self.log_text.see(tk.END)
        self.log_text.config(state="disabled")
    
    # 在主线程中更新UI
    self.root.after(0, _log)

进度显示

  • 进度条:显示整体进度百分比
  • 日志输出:显示详细的处理步骤
  • 颜色编码:不同类型的消息使用不同颜色
  • 时间戳:每条日志包含时间信息

日志级别

  • info:常规信息(蓝色)
  • success:成功信息(绿色)
  • warning:警告信息(橙色)
  • error:错误信息(红色)
  • highlight:重要信息(紫色)

8.3.3 用户交互设计

系统设计了友好的用户交互流程:

def toggle_analysis(self):
    """开始/停止分析的切换逻辑"""
    if self.btn_action['text'] == "🚀 开始评估":
        self.start_analysis()
    else:
        self.stop_analysis()

def stop_analysis(self):
    """停止当前分析任务"""
    if messagebox.askyesno("确认", "确定要停止当前任务吗?"):
        self.cancel_event.set()
        self.log("🛑 正在停止任务,请稍候...")
        self.btn_action.config(state="disabled")

def reset_ui_state(self):
    """重置UI状态"""
    self.btn_action.config(text="🚀 开始评估", style="Action.TButton", state="normal")

交互流程

  1. 文件选择:用户选择音视频文件
  2. 参数输入:输入用户基本信息
  3. 开始评估:点击"开始评估"按钮
  4. 进度查看:实时查看处理进度
  5. 结果查看:查看评估结果和详细报告
  6. 报告生成:自动生成评估报告

用户反馈

  • 按钮状态变化:开始/停止按钮状态切换
  • 进度指示:进度条和日志显示处理进度
  • 结果展示:清晰的结果展示和解释
  • 错误提示:友好的错误提示和处理建议

8.4 报告生成功能

8.4.1 报告模板设计

系统使用自定义报告模板生成评估报告:

def generate_report_image(result, user_info, output_path):
    """
    生成评估报告图片
    
    参数:
        result: 推理结果字典
        user_info: 用户信息字典
        output_path: 输出文件路径
    
    返回:
        bool: 是否生成成功
    """
    try:
        # 创建报告画布
        from PIL import Image, ImageDraw, ImageFont
        
        # 设置画布大小
        width, height = 800, 1200
        image = Image.new('RGB', (width, height), 'white')
        draw = ImageDraw.Draw(image)
        
        # 加载字体
        try:
            font_title = ImageFont.truetype("simhei.ttf", 36)
            font_header = ImageFont.truetype("simhei.ttf", 24)
            font_normal = ImageFont.truetype("simhei.ttf", 16)
            font_small = ImageFont.truetype("simhei.ttf", 12)
        except:
            font_title = ImageFont.load_default()
            font_header = ImageFont.load_default()
            font_normal = ImageFont.load_default()
            font_small = ImageFont.load_default()
        
        # 绘制标题
        draw.text((400, 50), "多模态抑郁风险评估报告", fill="#2c3e50", font=font_title, anchor="mm")
        
        # 绘制用户信息
        draw.text((100, 120), f"报告编号: {user_info.get('report_id', 'N/A')}", fill="#34495e", font=font_normal)
        draw.text((100, 160), f"姓名: {user_info.get('name', '未命名')}", fill="#34495e", font=font_normal)
        draw.text((400, 160), f"性别: {user_info.get('gender', '未知')}", fill="#34495e", font=font_normal)
        draw.text((650, 160), f"年龄: {user_info.get('age', '未知')}", fill="#34495e", font=font_normal)
        
        # 绘制评估结果
        draw.text((100, 220), "评估结果", fill="#3498db", font=font_header)
        draw.rectangle([(80, 260), (720, 380)], outline="#ddd", width=2)
        
        severity = result.get('severity', '无法评估')
        severity_color = result.get('severity_color', '#44aa44')
        draw.text((400, 300), severity, fill=severity_color, font=font_title, anchor="mm")
        
        # 绘制详细信息
        draw.text((100, 420), "详细信息", fill="#3498db", font=font_header)
        
        y_position = 460
        draw.text((100, y_position), f"抑郁概率: {result.get('proba', 0):.2%}", fill="#2c3e50", font=font_normal)
        y_position += 40
        draw.text((100, y_position), f"判定阈值: {result.get('threshold', 0):.2%}", fill="#2c3e50", font=font_normal)
        y_position += 40
        draw.text((100, y_position), f"PHQ-8分数: {result.get('phq8_score', 0):.1f}", fill="#2c3e50", font=font_normal)
        
        # 保存报告
        image.save(output_path)
        return True
        
    except Exception as e:
        print(f"报告生成失败: {e}")
        return False

报告内容

  • 报告标题:系统名称和报告类型
  • 用户信息:姓名、性别、年龄、报告编号
  • 评估结果:严重程度等级和颜色标识
  • 详细信息:概率值、阈值、PHQ-8分数
  • 风险因素:关键风险因素分析
  • 建议:针对性的建议和指导

设计特点

  • 清晰布局:信息层次分明,易于阅读
  • 色彩编码:使用颜色标识不同的严重程度
  • 专业字体:使用清晰易读的字体
  • 结构化信息:按照逻辑顺序组织信息

8.4.2 报告命名与保存

系统实现了智能的报告命名和保存机制:

def generate_report(self, result, user_info):
    """生成并保存评估报告"""
    # 获取保存目录
    save_dir = config_local.REPORT_SAVE_DIR
    if not os.path.exists(save_dir):
        os.makedirs(save_dir, exist_ok=True)
    
    # 生成报告文件名
    today_str = datetime.datetime.now().strftime('%Y%m%d')
    
    # 扫描目录下今日报告数量
    existing_files = [f for f in os.listdir(save_dir) 
                     if f.startswith(f"Report_{today_str}_") and f.endswith(".png")]
    daily_idx = len(existing_files) + 1
    idx_str = f"{daily_idx:03d}"
    
    # 报告ID
    report_id = f"{today_str}{idx_str}"
    user_info['report_id'] = report_id
    
    # 安全的文件名
    safe_name = user_info['name'].replace(" ", "_")
    filename = f"Report_{today_str}_{idx_str}_{safe_name}.png"
    report_path = os.path.join(save_dir, filename)
    
    # 生成报告
    if report_generator.generate_report_image(result, user_info, report_path):
        self.log(f"📄 报告已保存: {report_path}")
        
        # 添加打开报告按钮
        self.add_open_button(report_path)
        
        # 播放完成提示音
        utils.play_sound('success')
        
        # 自动打开报告
        try:
            os.startfile(report_path)
        except:
            pass
        
        return report_path
    else:
        self.log("❌ 报告生成失败")
        return None

命名规则

  • 格式:Report_日期_序号_姓名.png
  • 日期:YYYYMMDD格式
  • 序号:每日递增的3位数字
  • 姓名:用户姓名(处理空格)

保存策略

  • 目录管理:自动创建保存目录
  • 文件名安全:处理特殊字符和空格
  • 避免覆盖:确保文件名唯一
  • 版本控制:按日期和序号区分报告

用户体验

  • 自动保存:无需用户手动保存
  • 自动打开:生成后自动打开报告
  • 提示音:完成时播放提示音
  • 打开按钮:提供手动打开报告的按钮

🎯 本章总结

核心知识点回顾

  1. 推理引擎架构

    • ✅ 模型加载和管理机制
    • ✅ 特征处理和预处理流程
    • ✅ 自动模型选择和决策融合
  2. 结果解释机制

    • ✅ 特征贡献度分析
    • ✅ 风险因素识别
    • ✅ 结果可视化设计
  3. 错误处理策略

    • ✅ 异常捕获和处理
    • ✅ 错误信息反馈
    • ✅ 系统稳定性保障
  4. GUI界面开发

    • ✅ Tkinter组件和布局
    • ✅ 用户交互流程设计
    • ✅ 报告生成和管理

重点/易错点总结

重点掌握

  • 推理引擎的架构设计和工作流程
  • 多模态决策融合的实现方法
  • Tkinter GUI开发的最佳实践
  • 报告生成和用户体验设计

常见错误

  1. ❌ 线程安全问题:在非主线程更新UI
  2. ❌ 异常处理不完善:缺少异常捕获导致程序崩溃
  3. ❌ 资源管理不当:文件句柄未关闭
  4. ❌ 用户体验设计不佳:缺少反馈和错误提示

学习建议

  1. 实践练习:尝试修改和扩展GUI界面
  2. 架构学习:理解推理引擎的设计模式
  3. 用户体验:关注用户交互的流畅性
  4. 代码优化:学习线程安全和资源管理

📚 参考资源


下一章预告:[[卷五:实战篇]] – 完整项目实战和代码优化指南

未经允许,禁止转载
如有侵权,联系删除
上一篇
下一篇