本文说明当前实现。实时监测与离线分析采用不同处理方式;离线的时间分箱、固定幅值分箱和自由幅值分箱共用滤波曲线、峰谷及质量标记。
传感器 TCP 数据经 DataReceiver 编号后,进入实时处理与记录缓存。Socket.IO 推送原始值、滤波值、峰谷和呼吸指标。结束记录后,系统取记录范围及已有前后缓冲,重新离线滤波、检测峰谷并分箱。
| 模块 | 职责 |
|---|---|
breath_process/signal_processor.py |
滑动窗口调度与滤波结果输出 |
breath_process/filter_strategies.py |
实时、离线策略入口 |
breath_process/offline_quality.py |
离线滤波、局部伪影处理和质量区间 |
breath_process/filter_context.py |
统一缓冲范围及边缘保护长度 |
breath_process/peak_valley_detector.py |
峰谷检测、BPM 和稳定性 |
breath_process/respiratory_bins.py |
时间、固定幅值和自由幅值分箱 |
breath_process/record_file.py |
记录文件格式与兼容读取 |
上述路径均相对于 src/ct_breath/。HTTP 字段和调用示例见接口说明。
实时策略使用 Butterworth 带通 sosfilt,按配置加回原始窗口中位数,再进行 Gaussian 平滑,输出保留一位小数。实时页面默认带通为 0.10–0.67 Hz、1 阶,平滑参数为 1.8;直接调用接口且省略该参数时,模型默认值为 1。
滑动窗口最多保留 2,000 点,每次处理 5 个新点,至少积累 300 点后输出。50 Hz 下,启动积累时间约为 6 秒;启动等待与运行中的波形延迟不是同一概念。滤波器相位响应、窗口处理、峰谷确认和传输均可能影响显示时序,不能用一个固定延迟概括所有波形。
可选的实时峰谷确认使用 confirm_realtime_events 和 confirmation_delay_points;默认关闭,启用后的默认确认长度为 12 点,即 50 Hz 下约 240 ms。该配置不改变离线峰谷检测。
记录分析和模拟预览默认采用保留基线的双向低通:
| 参数 | 当前默认值 |
|---|---|
offline_filter |
baseline_lowpass |
| 低通上限 | 2.00 Hz |
lowpass_order |
4 |
gaussian_sigma |
1 |
| 峰谷灵敏度 | 标准,peak_threshold_ratio = 0.30 |
低通采用 Butterworth SOS 和 sosfiltfilt,之后进行 Gaussian 平滑。保留直流与慢变化,不额外加回基线,也不逐周期缩放或归一化。保留基线不等于消除设备漂移。
界面用单手柄滑块设置低通上限,范围为 0.01–20 Hz,且严格小于采样率的一半。Hz 与换算后的 BPM 保留两位小数。记录分析修改截止频率后需应用设置,才能重新滤波;修改灵敏度只重新检测峰谷和分箱。模拟预览还可设置阶数和平滑参数。
时间、固定幅值、自由幅值共用一次滤波结果。切换分箱方式、箱数、吸气方向或固定幅值上下限,只重新分箱,不重新滤波。
底层带通仍用于实时链路及旧接口。/applyFilter 未指定 offline_filter 时仍按旧接口默认值处理;当前离线 UI 会显式请求低通。/startReceive 和 /setRTFilterParams 不接受离线低通方案。
历史字段 highpass_cutoff 在低通方案中表示上限频率;lowpass_cutoff 和带通 order 不参与低通计算。自定义频率仍通过 bandpass_mode = "custom" 保存。
导入旧带通记录时保留其结果并标明旧版。只有用户明确重新计算且成功后,才用低通结果替换。旧带通的基线恢复逻辑也保留在兼容处理中,不属于当前低通流程。
TOML 配置统一控制离线前后缓冲,修改后重启生效:
[record]
pre_points = 200
post_points = 200默认在 50 Hz 下前后各 4 秒;允许设为 0。数据不足时使用已有部分,不补造样本。旧配置只有时间/幅值专用字段时,前后分别取两种配置的较大值;显式公共字段优先。
结束记录后会等待后缓冲,等待期限按剩余点数除以采样率再加 8 秒计算,超时采用已收到的数据。滤波使用缓冲,分箱只覆盖记录范围;离线降低截止频率不会增加已经采集的数据。
边缘保护与采集缓冲是两个概念。保护长度由实际滤波器最大极点模 r 估算:ceil(log(0.05) / log(r)) 个采样点,表示最慢极点的衰减估计,不是任意输入下的幅值误差保证。不同截止频率和阶数会得到不同长度。
离线处理先用二阶差分噪声估计、局部中位数残差和突变边缘检测干扰,原始数据始终保留。
- 孤立短尖峰仅在滤波工作副本中插值,其位置及邻近影响区间被排除。
- 前后有数据、已局部界定且不超过 1 秒的短暂伪影,可在工作副本中桥接,避免把两侧正常呼吸切成过短片段;桥接区间仍不参与分箱。
- 持续体动或数据缺口将滤波划分为独立处理段;不足 300 点的片段标记为
too_short。 - 边缘及局部伪影影响范围写入
filter_edge。局部影响依据滤波响应差异判断,不表示已恢复异常段的真实呼吸。
processingQuality.invalidIntervals 记录排除区间及原因。峰谷、默认幅值上下限和分箱都使用这些标记,跨越无效区间的半周期不分箱。因此,局部咳嗽不会直接导致整段记录被排除,但密集干扰可能使剩余片段无法形成完整呼吸段。
质量格式 v2 可在无效区间显示滤波参考值,界面用虚线区分;这些值不参与分箱。v1 记录中的原始占位值仍按旧语义隐藏,重新计算后才生成参考值。消费方应同时读取波形和质量标记。
峰谷检测使用 scipy.signal.find_peaks。自适应模式结合最小峰间距、信号突出程度和噪声阈值判断候选点;离线检测在有效连续区间内进行,并折叠连续同类事件。
低通方案根据局部候选峰谷突出度估计阈值,避免把整段慢基线跨度直接当作呼吸幅度。灵敏度分为保守 0.45、标准 0.30、灵敏 0.15;比例越低越容易保留小起伏,也可能增加误检。实际峰谷位置和值来自滤波曲线。
BPM 由有效相邻波峰的序号间隔和采样率计算。稳定性采用波峰间隔的变异系数:小于 0.15 为 stable,小于 0.35 为 variable,否则为 irregular;数据不足为 insufficient。这些指标不替代质量区间判断。
默认每周期 10 箱,支持 4、6、8、10、12、16、20 箱,吸气和呼气各占一半。默认信号上升代表吸气,可反转方向。
| 方式 | 分箱规则 |
|---|---|
| 时间 | 每段已识别的吸气或呼气按时间等分 |
| 固定幅值 | 同一组上下限按幅值等分,吸气和呼气分别编号;界外数据不分箱,浅呼吸可缺少部分箱 |
| 自由幅值 | 每个相邻峰谷段按自身峰谷幅值等分 |
固定幅值图用横向虚线表示幅值分界;其他方式用纵向虚线表示时间边界。默认固定上下限来自记录范围内有效滤波点的第 5、95 百分位值,排除缓冲和无效点。拖动上下限只更新分箱。
记录格式版本为 5,保留原始数据、公共滤波配置、analysis、质量标记及 respiratoryBins。分箱区间使用原始采样序号,交叉点可为小数,按 [start, end) 消费;同一个箱可对应多个区间。导入后按保存的设置和波形重新计算分箱。旧的按模式分别保存的滤波配置不再写入新文件。
uv run --frozen pytest -q
uv run --group ui-test playwright install chromium
uv run --group ui-test pytest tests/ui -q当前模拟对比报告及复算入口见脚本说明。报告的测算数据与应用行为分开保存,不作为真实患者数据的准确度证明。