feat: v3.5 - Multi-language acoustic algorithm scheduler
- Core: Python/C++/MATLAB tri-protocol bridge architecture - Validation: 5-type real-time field, type, dimension, completeness, and compatibility checking - UI: Premium Deep Sea theme with visual pipeline preview - Modules: Sea Trial Exclusive + Exception Hub + Algorithm Management - Tests: Full coverage with 5 automated test cases
这个提交包含在:
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE settings
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.log
|
||||||
|
tmp_*.mat
|
||||||
|
*.npy
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Testing artifacts
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# 水声算法全协议集成与调度平台
|
||||||
|
|
||||||
|
轻量化、高适配、零门槛的水声算法纳管与运行环境。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
- **多语言算法适配**:Python / MATLAB / C / C++ 算法一键纳管
|
||||||
|
- **零代码接口配置**:复杂接口即插即用,实时校验
|
||||||
|
- **智能调度执行**:可视化流程组合,跨语言混编
|
||||||
|
- **三层容错机制**:配置拦截 → 自动修复 → 隔离/兜底
|
||||||
|
- **海试专属模式**:一键质量检测,秒级出结果
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 启动 Web 界面
|
||||||
|
streamlit run main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
acoustic_algo_scheduler/
|
||||||
|
├── main.py # Streamlit Web 入口
|
||||||
|
├── docs/ # 需求与设计文档
|
||||||
|
│ ├── 水声算法全协议集成与调度平台 - 需求规格说明书.md
|
||||||
|
│ ├── 水声算法全协议集成与调度平台 - 技术实施方案文档.md
|
||||||
|
│ └── walkthrough_2_5.md
|
||||||
|
│ └── walkthrough_final.md
|
||||||
|
├── src/
|
||||||
|
│ ├── models.py # WaterSoundData 统一数据标准
|
||||||
|
│ ├── bridges.py # 多语言算法桥接器
|
||||||
|
│ └── scheduler.py # 核心调度器
|
||||||
|
├── tests/ # 测试用例
|
||||||
|
└── requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Streamlit (Web界面)
|
||||||
|
- NumPy / SciPy (数据处理)
|
||||||
|
- ctypes / cffi (C/C++ 桥接)
|
||||||
|
- matlab.engine / oct2py (MATLAB 桥接)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "预处理算子",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "去直流、带通滤波(默认10-400Hz)",
|
||||||
|
"type": "python",
|
||||||
|
"module": "src.sample_algorithms",
|
||||||
|
"function": "dc_removal_and_bandpass",
|
||||||
|
"inputs": {
|
||||||
|
"raw_data": {
|
||||||
|
"field": "raw_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "原始声呐数据"
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"field": "sample_rate",
|
||||||
|
"type": "float",
|
||||||
|
"description": "采样率"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"clean_data": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "预处理后数据"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "时频分析",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "STFT 幅度谱计算",
|
||||||
|
"type": "python",
|
||||||
|
"module": "src.sample_algorithms",
|
||||||
|
"function": "stft_magnitude",
|
||||||
|
"inputs": {
|
||||||
|
"clean_data": {
|
||||||
|
"field": "clean_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "清洗后数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"stft_data": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "STFT矩阵"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SNR估计",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "基于 RMS/Var 的简易信噪比估计",
|
||||||
|
"type": "python",
|
||||||
|
"module": "src.sample_algorithms",
|
||||||
|
"function": "snr_estimation",
|
||||||
|
"inputs": {
|
||||||
|
"raw_data": {
|
||||||
|
"field": "raw_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "待评估数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"snr_value": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "float",
|
||||||
|
"description": "信噪比(dB)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "质量巡检",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "掉点、饱和与数据完整性检测",
|
||||||
|
"type": "python",
|
||||||
|
"module": "src.sample_algorithms",
|
||||||
|
"function": "quality_check",
|
||||||
|
"inputs": {
|
||||||
|
"raw_data": {
|
||||||
|
"field": "raw_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "待校验原始数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"core_index": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "dict",
|
||||||
|
"description": "质量指标集"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "C++ 增益算子",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "使用 C++ 侧实现的高效增益处理(1.5x)",
|
||||||
|
"type": "cpp",
|
||||||
|
"lib_path": "src/sample_algos_cpp/simple_ops.so",
|
||||||
|
"function": "multiply_by_factor_cpp",
|
||||||
|
"inputs": {
|
||||||
|
"in_data": {
|
||||||
|
"field": "clean_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "输入数据"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"gain_data": {
|
||||||
|
"type": "ndarray",
|
||||||
|
"index": 0,
|
||||||
|
"description": "增益后数据"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MATLAB 带通算子",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Antigravity",
|
||||||
|
"description": "基于 MATLAB .m 实现的带通滤波",
|
||||||
|
"type": "matlab",
|
||||||
|
"m_path": "src/sample_algos_matlab",
|
||||||
|
"function": "bandpass_filter_m",
|
||||||
|
"inputs": {
|
||||||
|
"in_data": {
|
||||||
|
"field": "raw_data",
|
||||||
|
"type": "ndarray",
|
||||||
|
"description": "输入数据"
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"field": "sample_rate",
|
||||||
|
"type": "float",
|
||||||
|
"description": "采样率"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"m_filtered": {
|
||||||
|
"type": "ndarray",
|
||||||
|
"index": 0,
|
||||||
|
"description": "滤波后数据"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Walkthrough - Phase 2.5(可演示级 MVP)
|
||||||
|
|
||||||
|
Phase 2.5 将系统从“框架可跑”推进到“可演示、可做现场巡检”的状态,重点补齐真实算法、海试专属流程与异常聚合能力。
|
||||||
|
|
||||||
|
## 1) 真实水声算法库
|
||||||
|
实现于 `src/sample_algorithms.py`:
|
||||||
|
- `dc_removal_and_bandpass`: 去直流 + 带通滤波(含参数与短序列兜底)
|
||||||
|
- `stft_magnitude`: STFT 幅度谱
|
||||||
|
- `snr_estimation`: 简易 SNR 估计(dB)
|
||||||
|
- `quality_check`: 掉点/饱和巡检,输出 `quality_score` 等指标
|
||||||
|
|
||||||
|
对应元数据已更新至 `data/algorithms.json`(含中文描述与输入输出字段映射)。
|
||||||
|
|
||||||
|
## 2) 海试专属(⛴️ 一键质量巡检)
|
||||||
|
实现于 `main.py` 的“海试专属”页:
|
||||||
|
- 内置固定流程:去直流+带通 → 质量巡检 → SNR 估计
|
||||||
|
- 指标看板:SNR、最大幅值、质量得分
|
||||||
|
- 一键导出 `CSV` 报告
|
||||||
|
|
||||||
|
## 3) 异常中心(🚑)
|
||||||
|
实现于 `main.py` 的“异常中心”页:
|
||||||
|
- 聚合最近一次链路校验结果(字段/类型/维度/完整性/兼容性)
|
||||||
|
- 聚合最近一次执行结果(状态、错误信息、执行日志)
|
||||||
|
- 支持导出 `issue_bundle.json`
|
||||||
|
- 环境自检入口:`src/env_detect.py`
|
||||||
|
|
||||||
|
## 4) 加固项
|
||||||
|
- `src/bridges.py`: `normalize` 预处理算子增加除零与空数组保护
|
||||||
|
- `src/models.py`: 日志统一写入 UTC 时间戳
|
||||||
|
|
||||||
|
## 5) 验证
|
||||||
|
运行 `python tests/test_core.py` 覆盖:
|
||||||
|
- 基础链路集成测试
|
||||||
|
- 校验拦截(缺字段/类型/维度)
|
||||||
|
- 海试一键巡检链路(含真实算法调用)
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Walkthrough - v3.5(最终交付)
|
||||||
|
|
||||||
|
本版本将平台推进为“可演示/可落地”的全协议集成与调度工具:支持 Python/C++/MATLAB(含降级) 三语言算法编排执行,具备五维校验、环境自检、海试一键巡检与异常中心排障闭环。
|
||||||
|
|
||||||
|
## 1) 设计与交互
|
||||||
|
- 深海风 UI:深蓝侧边栏、现代排版、卡片化布局(`main.py`)
|
||||||
|
- 可视化流水线:调度页提供“运行流水线预览”(`main.py`)
|
||||||
|
- 语言标识:Python/C++/MATLAB 徽标在算法库与流水线中可视化区分(`main.py`)
|
||||||
|
|
||||||
|
## 2) 多协议桥接
|
||||||
|
- Python:动态加载并执行(`src/algo_loader.py`、`src/bridges.py`)
|
||||||
|
- C++:`.so/.dll` 动态库加载 + `ctypes` 调用(`src/bridges.py`、示例 `src/sample_algos_cpp/`)
|
||||||
|
- MATLAB/Octave:Engine → oct2py → CLI+matfile 三级降级(`src/bridges.py`、示例 `src/sample_algos_matlab/`)
|
||||||
|
|
||||||
|
## 3) 工业级自检与五维校验
|
||||||
|
- 环境自检:编译器、MATLAB/Octave 能力检测(`src/env_detect.py`)
|
||||||
|
- 五维校验:字段/类型/维度/完整性/环境兼容(`src/validator.py`、调度前拦截 `src/scheduler.py`)
|
||||||
|
|
||||||
|
## 4) 海试专属与异常中心
|
||||||
|
- 海试一键巡检:内置链路 + CSV 报告导出(`main.py`)
|
||||||
|
- 异常中心:校验问题卡片、执行日志、环境快照、排障 JSON 包导出(`main.py`)
|
||||||
|
|
||||||
|
## 5) 验证方式
|
||||||
|
运行测试:
|
||||||
|
```bash
|
||||||
|
python tests/test_core.py
|
||||||
|
```
|
||||||
|
|
||||||
|
启动界面:
|
||||||
|
```bash
|
||||||
|
streamlit run main.py
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# 水声算法全协议集成与调度平台 - 技术实施方案文档
|
||||||
|
文档信息
|
||||||
|
项目 内容
|
||||||
|
文档版本 V1.0
|
||||||
|
编写日期 2026.01
|
||||||
|
适用范围 软件研发、模块开发、技术实现、测试部署
|
||||||
|
研发团队 2-3 人,核心技术栈 Python
|
||||||
|
一、技术方案总体设计
|
||||||
|
1.1 设计理念
|
||||||
|
基于 **“轻量化、插件化、标准化、低耦合”** 核心设计理念,贴合微型研发团队能力,优先采用 Python 生态成熟技术,同时融合 C/C++ 高性能计算优势,打造 “上层 Python 调度交互 + 底层 C/C++ 高性能计算” 的混合技术栈,实现多语言算法统一适配,同时预留工业级拓展接口。
|
||||||
|
1.2 整体架构
|
||||||
|
采用分层式 + 插件化架构设计,分为用户交互层、核心调度层、桥接插件层、算法层、数据层五层,所有层之间通过标准化协议对接,低耦合、可插拔,便于开发、维护与拓展。
|
||||||
|
plaintext
|
||||||
|
用户交互层(Web界面)←→核心调度层(标准化内核)←→桥接插件层(语言专属)←→算法层(标准/自研,多语言)
|
||||||
|
↓
|
||||||
|
数据层(标准化中间数据)
|
||||||
|
1.2.1 各层核心职责
|
||||||
|
用户交互层:基于 Streamlit 实现 Web 可视化界面,负责算法上传、接口配置、流程组合、异常展示、日志查看等客户操作,为客户提供零代码交互体验;
|
||||||
|
核心调度层:软件核心内核,负责标准化中间数据管理、算法流程调度、接口实时校验、全流程容错、日志记录,是所有功能的调度中枢;
|
||||||
|
桥接插件层:包含 Python/MATLAB/C/C++ 语言专属桥接插件,负责跨语言数据转换、算法调用、编译封装,屏蔽语言差异,对上层暴露标准化接口;
|
||||||
|
算法层:包含官方标准算法库与客户自定义算法库,所有算法均为黑盒化插件,遵循标准化接口协议;
|
||||||
|
数据层:以 WaterSoundData 为核心,负责全流程数据的存储、流转与管理,同时包含日志、报告、模板等持久化数据的存储。
|
||||||
|
1.3 核心设计原则
|
||||||
|
标准化:定义统一的标准化中间数据格式、黑盒插件协议、跨语言数据转换协议,所有模块均遵循标准化协议对接;
|
||||||
|
插件化:桥接插件、预处理算子、算法黑盒均为可插拔式设计,新增 / 修改 / 删除不影响核心内核;
|
||||||
|
低耦合:各层之间、模块之间通过标准化接口对接,无直接代码依赖,便于单独开发、测试与迭代;
|
||||||
|
轻量化:优先采用 Python 轻量开源库,避免重型框架,保证客户侧部署零成本、运行低资源;
|
||||||
|
容错性:全流程融入容错设计,从配置到执行层层校验、兜底,保证系统稳定性;
|
||||||
|
可拓展:预留算法语言、预处理算子、功能模块的拓展接口,支持后续快速升级,同时预留 C++/Qt 工业级拓展接口,无技术断层。
|
||||||
|
二、核心技术选型
|
||||||
|
2.1 开发语言
|
||||||
|
主开发语言:Python3.8+(团队核心技术栈,开发效率高,生态丰富,跨平台,轻量);
|
||||||
|
辅助语言:C/C++(仅用于 C/C++ 算法编译为动态链接库,无需自研业务代码)。
|
||||||
|
2.2 核心技术栈与第三方库
|
||||||
|
按功能模块梳理核心技术 / 库,均为工业级成熟方案,无前沿未验证技术,降低研发风险:
|
||||||
|
功能模块 核心技术 / 第三方库 核心作用
|
||||||
|
用户交互层 Streamlit 极简开发 Web 可视化界面,支持下拉、拖拽、实时校验、日志展示,无需前端开发
|
||||||
|
核心调度层 Python 原生 OOP、dataclasses、logging、json 实现标准化中间数据定义、流程调度、实时校验、结构化日志、数据持久化
|
||||||
|
桥接插件层 Python:原生调用
|
||||||
|
MATLAB:matlab.engine、oct2py
|
||||||
|
C/C++:ctypes、cffi 跨语言数据转换、算法调用,oct2py 为 MATLAB 无授权兜底,ctypes/cffi 实现 C/C++ 库加载
|
||||||
|
C/C++ 编译模块 MinGW/GCC、Python subprocess/os/glob 自动检测编译器、生成编译脚本,将 C/C++ 文件编译为跨平台动态链接库
|
||||||
|
数据处理模块 NumPy、SciPy 水声算法核心数据处理,与现有水声算法代码无缝兼容,实现数组、矩阵操作
|
||||||
|
性能优化模块 Python multiprocessing、mmap(内存映射) 实现非依赖型算法并行调度、大体积数据分块处理、内存级高性能数据传递
|
||||||
|
数据持久化模块 Python 原生文件操作(.npy/.mat/.bin/.json/.pdf/Excel) 实现算法版本、模板、日志、报告、数据的存储与导出,无需复杂数据库
|
||||||
|
报告生成模块 reportlab、pandas 实现检测报告、对比报告的生成与导出(PDF/Excel 格式)
|
||||||
|
2.3 技术栈优势
|
||||||
|
研发效率高:Python 生态成熟,Streamlit 快速实现 Web 界面,无需前端 / 后端分离开发,2-3 人团队 1-2 周可完成核心功能开发;
|
||||||
|
客户侧零成本:跨平台免编译,仅需 Python 运行环境,轻量依赖一键安装,拷贝即可运行;
|
||||||
|
低耦合可拓展:插件化设计,新增功能 / 算法语言仅需开发对应插件,无需重构核心代码;
|
||||||
|
性能有保障:融合 C/C++ 高性能计算优势,内存映射实现无损耗数据传递,满足水声算法处理需求;
|
||||||
|
维护成本低:Python 代码简洁可读性强,模块化设计,团队可互相维护,客户问题可快速定位解决。
|
||||||
|
三、核心模块技术实现
|
||||||
|
3.1 标准化中间数据模块(数据层核心)
|
||||||
|
3.1.1 数据定义
|
||||||
|
基于 Python dataclasses定义WaterSoundData类,作为全流程唯一数据交换标准,包含基础数据、过程数据、结果数据、元信息四大类字段,支持动态扩展字段,适配个性化需求:
|
||||||
|
python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, Optional, List
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WaterSoundData:
|
||||||
|
# 基础原始数据
|
||||||
|
raw_data: np.ndarray # 原始声呐数据
|
||||||
|
sample_rate: float # 采样率
|
||||||
|
collect_time: str # 数据采集时间
|
||||||
|
sea_env: Dict = None # 海洋环境参数(声速、温度、盐度等)
|
||||||
|
# 处理过程数据
|
||||||
|
clean_data: Optional[np.ndarray] = None # 清洗后数据
|
||||||
|
denoised_data: Optional[np.ndarray] = None# 降噪后数据
|
||||||
|
stft_data: Optional[np.ndarray] = None # STFT时频数据
|
||||||
|
feat_data: Optional[np.ndarray] = None # 特征数组
|
||||||
|
# 结果数据
|
||||||
|
final_result: Optional[np.ndarray] = None# 最终处理结果
|
||||||
|
core_index: Dict = None # 核心指标(信噪比、数据完整性等)
|
||||||
|
vis_data: Dict = None # 可视化数据
|
||||||
|
# 元信息
|
||||||
|
status: str = "init" # 数据状态:init/processing/finished/error
|
||||||
|
error_info: str = "" # 错误信息
|
||||||
|
log: List[Dict] = None # 执行日志
|
||||||
|
algo_version: Dict = None # 算法版本记录
|
||||||
|
3.1.2 核心方法
|
||||||
|
为 WaterSoundData 类实现数据更新、日志添加、错误记录、数据保存 / 加载等核心方法,支持全流程数据的动态管理与溯源。
|
||||||
|
3.2 桥接插件层实现
|
||||||
|
采用插件化工厂模式,为每种语言开发独立的桥接插件,核心实现数据转换与算法调用两大功能,对外暴露统一的call_algo方法,核心调度层无需感知语言差异。
|
||||||
|
3.2.1 Python 桥接插件(最简)
|
||||||
|
直接调用 Python 函数,实现标准化数据与 Python 算法输入输出的映射,核心代码示例:
|
||||||
|
python
|
||||||
|
class PythonBridge:
|
||||||
|
def __init__(self, algo_func, input_mapping, output_mapping):
|
||||||
|
self.algo_func = algo_func # Python算法核心函数
|
||||||
|
self.input_mapping = input_mapping # 输入映射关系
|
||||||
|
self.output_mapping = output_mapping# 输出映射关系
|
||||||
|
|
||||||
|
def call_algo(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
# 提取标准化输入并映射
|
||||||
|
algo_input = [getattr(water_data, k) for k in self.input_mapping.values()]
|
||||||
|
# 调用算法
|
||||||
|
algo_output = self.algo_func(*algo_input)
|
||||||
|
# 输出映射回标准化数据
|
||||||
|
for k, v in zip(self.output_mapping.keys(), algo_output):
|
||||||
|
setattr(water_data, k, v)
|
||||||
|
return water_data
|
||||||
|
3.2.2 MATLAB 桥接插件
|
||||||
|
基于matlab.engine/oct2py实现,自动完成 numpy 数组与 MATLAB 矩阵的转换,核心实现引擎自动管理、数据转换、容错兜底:
|
||||||
|
调用前自动检测 MATLAB 环境,有则用matlab.engine,无则切换oct2py;
|
||||||
|
实现 numpy.ndarray ↔ matlab.double/oct2py.Array 的无损转换;
|
||||||
|
引擎调用失败则切换为.mat文件桥接模式,通过文件实现数据传递。
|
||||||
|
3.2.3 C/C++ 桥接插件
|
||||||
|
核心实现C/C++ 文件编译与动态链接库调用两大功能,同时完成 numpy 数组与 C/C++ 指针 / 数组的高性能转换:
|
||||||
|
自动编译:检测客户编译器,自动生成编译脚本,将 C/C++ 文件编译为.dll(Windows)/.so(Linux)/.dylib(macOS);
|
||||||
|
库加载:通过ctypes加载动态链接库,绑定核心函数;
|
||||||
|
数据转换:采用内存映射实现 numpy 数组与 C/C++ 指针的直接映射,无数据拷贝,保证高性能;
|
||||||
|
参数传递:自动传递数组长度、维度等信息,保证 C/C++ 算法正确执行。
|
||||||
|
3.3 核心调度层实现
|
||||||
|
核心调度层是软件的 “大脑”,负责接口校验、流程调度、容错处理、日志记录,基于状态机模式实现算法流程的自动化调度,核心流程:
|
||||||
|
流程解析:解析客户拖拽的流程 / 文字指令生成的流程,得到算法黑盒列表与执行顺序;
|
||||||
|
全局校验:校验算法之间的接口衔接性,生成校验报告;
|
||||||
|
初始化:初始化 WaterSoundData,加载输入数据;
|
||||||
|
按序调度:遍历算法黑盒列表,依次调用对应桥接插件的call_algo方法,传递 WaterSoundData;
|
||||||
|
容错处理:若某算法调用失败,触发容错机制(隔离 / 兜底),记录错误信息,继续调度后续算法;
|
||||||
|
结果处理:所有算法执行完成后,生成处理结果与报告,更新 WaterSoundData 状态为finished。
|
||||||
|
3.3.1 实时校验模块实现
|
||||||
|
基于规则引擎实现接口配置的实时校验,将 5 类校验规则封装为独立的校验函数,配置操作触发时实时调用,返回校验结果与错误信息,核心示例:
|
||||||
|
python
|
||||||
|
def check_field_exist(water_data, field_name, pre_algo_list):
|
||||||
|
"""字段存在性校验"""
|
||||||
|
if hasattr(water_data, field_name):
|
||||||
|
if getattr(water_data, field_name) is not None:
|
||||||
|
return True, ""
|
||||||
|
# 校验前序算法是否能生成该字段
|
||||||
|
field_algo_map = {"stft_data": "STFT", "denoised_data": "Denoise"}
|
||||||
|
if field_name in field_algo_map and field_algo_map[field_name] not in pre_algo_list:
|
||||||
|
return False, f"该字段未生成,前序流程需添加{field_algo_map[field_name]}算法"
|
||||||
|
return False, f"字段{field_name}不存在或未初始化"
|
||||||
|
所有校验函数通过统一的校验入口调用,若有任意一个校验失败,弹窗提示并禁止保存配置。
|
||||||
|
3.3.2 容错处理模块实现
|
||||||
|
基于异常捕获 + 策略模式实现三层容错,将不同的容错策略封装为独立的函数,根据错误类型自动触发对应策略:
|
||||||
|
配置阶段:校验函数返回 False 则触发拦截策略,禁止保存;
|
||||||
|
转换阶段:数据转换异常则触发自动修复策略(维度展平、类型转换等),修复后继续执行;
|
||||||
|
执行阶段:算法调用异常则触发隔离策略/兜底策略,根据算法重要性自动选择,核心示例:
|
||||||
|
python
|
||||||
|
def fault_tolerance(water_data, algo_black_box, error_type, error_info):
|
||||||
|
"""容错处理主函数"""
|
||||||
|
water_data.error_info = error_info
|
||||||
|
water_data.log.append({"step": algo_black_box.name, "error": error_type, "info": error_info})
|
||||||
|
# 核心算法,触发兜底策略
|
||||||
|
if algo_black_box.is_core:
|
||||||
|
water_data.log.append({"step": algo_black_box.name, "action": "启用兜底标准算法"})
|
||||||
|
core_algo = get_standard_algo(algo_black_box.func_type) # 获取同功能标准算法
|
||||||
|
return core_algo.call_algo(water_data)
|
||||||
|
# 非核心算法,触发隔离策略
|
||||||
|
else:
|
||||||
|
water_data.log.append({"step": algo_black_box.name, "action": "算法隔离,继续执行后续流程"})
|
||||||
|
water_data.status = "part_error"
|
||||||
|
return water_data
|
||||||
|
3.4 用户交互层实现
|
||||||
|
基于Streamlit实现 Web 可视化界面,无需前端开发,纯 Python 代码实现所有交互功能,核心设计原则:简洁直观、功能分区、操作引导,界面分为算法库管理、接口配置、流程调度、海试模式、异常中心、日志报告六大功能区,核心实现:
|
||||||
|
算法上传:通过st.file_uploader实现文件上传,支持多文件 / 压缩包,配合输入框获取算法名称、核心函数名等信息;
|
||||||
|
接口配置:通过st.selectbox实现字段下拉选择,st.button实现预处理算子添加,实时调用校验函数,st.error实现错误提示;
|
||||||
|
流程组合:通过st.columns+st.drag_and_drop实现算法拖拽排序,生成流程列表;
|
||||||
|
执行调度:通过st.button触发调度函数,st.progress实现进度条,st.text实现实时日志展示;
|
||||||
|
异常中心:通过st.expander实现异常信息折叠展示,配合st.button实现一键修复;
|
||||||
|
日志报告:通过st.dataframe展示日志,st.download_button实现日志 / 报告导出。
|
||||||
|
Streamlit 实现的界面支持本地离线运行,无需搭建服务器,客户启动命令仅需:streamlit run main.py,操作极简。
|
||||||
|
3.5 海试专属模块实现
|
||||||
|
基于核心调度层做轻量化定制,隐藏复杂配置功能,仅保留核心操作,核心实现:
|
||||||
|
模式切换:通过 Web 界面一键切换 “科研模式 / 海试模式”,海试模式下仅显示 “数据上传、一键检测、报告导出” 三个核心模块;
|
||||||
|
专属流程:内置海试数据质量检测流程模板,无需客户配置,直接调用;
|
||||||
|
快速执行:关闭并行调度、非核心日志记录等功能,优化资源占用,提升执行速度;
|
||||||
|
简洁报告:定制海试专属检测报告,突出核心指标、检测结论、处理建议,支持 PDF/Excel 快速导出。
|
||||||
|
四、研发与部署计划
|
||||||
|
4.1 研发计划(2-3 人团队,总周期 3 周,分阶段执行)
|
||||||
|
阶段 1:核心框架开发(第 1 周)
|
||||||
|
完成标准化中间数据模块、核心调度层、Python/MATLAB 桥接插件开发;
|
||||||
|
完成基础 Web 界面(算法库管理、简单接口配置)开发;
|
||||||
|
实现单语言算法的上传、调用、执行核心流程。
|
||||||
|
阶段 2:功能完善(第 2 周)
|
||||||
|
完成 C/C++ 桥接插件、自动编译模块开发,支持 C/C++ 算法上传与调用;
|
||||||
|
完成接口配置实时校验、容错处理模块开发;
|
||||||
|
完成流程调度、文字指令解析功能开发;
|
||||||
|
完善 Web 界面,实现流程拖拽、异常展示、日志记录功能。
|
||||||
|
阶段 3:优化与测试(第 3 周)
|
||||||
|
完成海试专属模块、报告生成模块开发;
|
||||||
|
完成性能优化(分块处理、并行调度、内存映射);
|
||||||
|
进行全功能测试、性能测试、稳定性测试,修复 bug;
|
||||||
|
完成用户操作手册、技术文档编写,打包产品。
|
||||||
|
4.2 测试计划
|
||||||
|
分单元测试、集成测试、系统测试、客户试用测试四个阶段,由研发团队自行完成,贴合微型团队特点:
|
||||||
|
单元测试:各模块开发完成后,编写单元测试用例,测试核心函数的正确性;
|
||||||
|
集成测试:各模块集成后,测试模块之间的对接是否正常,跨语言算法调用是否稳定;
|
||||||
|
系统测试:按需求文档验收标准,测试全功能、性能、稳定性、易用性;
|
||||||
|
客户试用测试:邀请 2-3 家目标客户试用,收集反馈,修复问题,优化体验。
|
||||||
|
4.3 部署与交付计划
|
||||||
|
4.3.1 产品打包
|
||||||
|
核心代码:保留纯 Python 脚本,方便后续升级与定制;
|
||||||
|
便携包:通过pyinstaller将核心代码打包为单文件可执行程序(Windows/.exe、Linux/.bin、macOS/.app),客户无需安装 Python,双击即可运行;
|
||||||
|
配套文件:包含用户操作手册、技术支持联系方式、一键安装依赖脚本。
|
||||||
|
4.3.2 客户侧部署
|
||||||
|
客户侧部署零配置、零成本,两种部署方式:
|
||||||
|
脚本运行:客户安装 Python3.8+,解压产品包,运行install_deps.py一键安装依赖,再运行streamlit run main.py启动软件;
|
||||||
|
可执行程序运行:直接解压便携包,双击可执行程序即可启动,无需安装任何环境。
|
||||||
|
4.3.3 交付物
|
||||||
|
软件产品包(脚本版 + 便携版);
|
||||||
|
《用户操作手册》(图文版,含操作步骤、常见问题解决);
|
||||||
|
《技术支持手册》(含联系方式、问题反馈流程);
|
||||||
|
算法示例包(含官方标准算法、多语言算法示例)。
|
||||||
|
五、长远技术迭代与拓展
|
||||||
|
5.1 短期迭代(0-6 个月)
|
||||||
|
基于客户反馈优化 Web 界面体验,完善预处理算子库,新增水声行业专属预处理算子;
|
||||||
|
支持更多算法语言(如 Fortran/Julia),仅需开发对应桥接插件;
|
||||||
|
优化 C/C++ 编译与调用逻辑,提升高性能计算效率;
|
||||||
|
增加算法参数可视化调参功能,支持客户在 Web 界面直接修改算法参数。
|
||||||
|
5.2 中期迭代(6-18 个月)
|
||||||
|
增加团队协作功能,支持多用户共享算法库、模板,实现权限管理;
|
||||||
|
增加GPU 加速支持,对接 CUDA,实现大体积数据 GPU 加速处理;
|
||||||
|
优化数据处理模块,支持更多水声行业数据格式;
|
||||||
|
开发远程技术支持模块,实现日志远程上传、问题快速定位。
|
||||||
|
5.3 长期拓展(18 + 个月,工业级方向)
|
||||||
|
基于现有标准化协议,实现向 C++/Qt 工业级系统的平滑拓展,核心步骤:
|
||||||
|
基于 C++ 重构核心调度层,复用现有 WaterSoundData 标准化数据定义,保证数据互通;
|
||||||
|
将现有 Python 桥接插件移植为 C++ 版本,支持多语言算法调用;
|
||||||
|
基于 Qt 开发工业级可视化界面,支持硬件对接(声呐采集卡、FPGA、GPU);
|
||||||
|
形成 **“Python 轻量化科研工具”+“C++/Qt 工业级系统”** 双产品体系,实现科研与工程的无缝衔接。
|
||||||
|
六、风险评估与应对措施
|
||||||
|
风险类型 具体风险 应对措施
|
||||||
|
技术风险 C/C++ 跨平台编译兼容性问题 统一采用 MinGW/GCC 编译器,生成跨平台动态链接库,增加编译失败兜底方案
|
||||||
|
技术风险 MATLAB 无授权导致调用失败 集成 oct2py 开源库,自动检测 MATLAB 环境,无授权则自动切换 oct2py 兜底
|
||||||
|
性能风险 大体积数据处理效率低 实现自动分块处理、内存映射、并行调度,优化数据转换逻辑,无拷贝传递
|
||||||
|
研发风险 团队人员少,研发进度滞后 采用模块化开发,优先实现核心功能,非核心功能可后期迭代;合理分配任务,聚焦核心模块
|
||||||
|
客户风险 客户操作习惯不适应,上手困难 编写详细的图文操作手册,界面增加操作引导弹窗,提供 1v1 技术支持与操作培训
|
||||||
|
客户风险 客户自研算法复杂,适配失败 提供极简代码扩展方式,增加自定义预处理插件,同时提供专业的算法适配技术支持
|
||||||
|
七、质量保障
|
||||||
|
代码质量:遵循 Python PEP8 编码规范,添加详细的代码注释,核心函数编写文档字符串,保证代码可读性与可维护性;
|
||||||
|
测试覆盖:单元测试覆盖核心函数,集成测试覆盖模块对接,系统测试覆盖全功能,测试用例包含正常场景与异常场景;
|
||||||
|
文档完善:编写完整的需求文档、技术实施方案文档、用户操作手册、技术支持手册,保证研发、测试、交付、使用全流程有文档可依;
|
||||||
|
持续优化:基于客户反馈,建立 bug 修复与功能迭代机制,定期发布小版本更新,提升软件质量与体验。
|
||||||
|
八、总结
|
||||||
|
本技术实施方案基于 Python 生态,融合 C/C++ 高性能计算优势,采用分层式 + 插件化架构设计,贴合 2-3 人微型水声算法团队的研发能力与节奏,能快速实现软件的开发、测试与部署。方案既满足当前目标客户(高校 / 研究所 / 海试)的轻量化、零门槛、多语言适配核心需求,又预留了长远的工业级拓展接口,实现 “科研工具→工业级系统” 的平滑迭代。
|
||||||
|
软件的核心价值并非技术栈本身,而是通过标准化、插件化、容错化的设计,为水声算法研发人员解决工程适配痛点,让客户聚焦算法本身,同时为公司打造水声算法调度领域的差异化竞争力,支撑商业长远发展。
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# 水声算法全协议集成与调度平台 - 需求规格说明书
|
||||||
|
文档信息
|
||||||
|
项目 内容
|
||||||
|
文档版本 V1.0
|
||||||
|
编写日期 2026.01
|
||||||
|
适用范围 软件研发、测试、交付、客户使用指导
|
||||||
|
目标客户 高校水声实验室、水声领域研究所、海试现场测试团队
|
||||||
|
一、项目背景与目标
|
||||||
|
1.1 项目背景
|
||||||
|
当前水声算法研发场景中,存在多语言算法混用(Python/MATLAB/C/C++)、接口定义混乱、算法迭代频繁、跨语言调用复杂等问题,高校 / 研究所科研人员需投入大量精力解决工程适配问题,而非聚焦算法本身;海试现场则面临设备轻量化、操作零门槛、离线处理、快速出结果的核心诉求,现有工具无法同时满足科研试错与工程应用的双重需求。
|
||||||
|
本公司作为 2-3 人规模的水声算法科技企业,依托 Python 核心技术栈,打造一款轻量化、高适配、零门槛的水声算法智能调度处理软件,解决行业核心痛点。
|
||||||
|
1.2 项目目标
|
||||||
|
功能目标:实现多语言算法统一纳管、复杂接口零代码适配、跨语言算法自由组合调度、全流程自动化处理与容错溯源,支持海试数据质量检测专属场景。
|
||||||
|
性能目标:轻量化离线部署,客户侧零硬件成本,支持大体积水声数据分块处理,跨语言算法调用性能与原生调用持平,整体容错率≥95%。
|
||||||
|
体验目标:Web 界面可视化操作,全程零代码 / 极简代码,客户无需掌握工程开发知识,10 分钟即可上手,错误可自主快速修复。
|
||||||
|
商业目标:贴合微型企业研发节奏,快速落地商业化,同时预留工业级拓展接口,支撑长远业务发展。
|
||||||
|
二、核心需求
|
||||||
|
2.1 总体功能需求
|
||||||
|
软件整体分为算法库管理模块、接口配置模块、算法调度模块、数据处理模块、容错溯源模块、客户扩展模块、海试专属模块七大核心模块,所有功能均通过 Web 界面实现,支持本地离线运行,无网络依赖。
|
||||||
|
2.2 详细功能需求
|
||||||
|
2.2.1 算法库管理模块
|
||||||
|
双仓库管理
|
||||||
|
内置官方标准算法库:包含水声通用算法(降噪、特征提取、频谱分析、数据清洗、质量检测等),支持一键调用、免费迭代更新。
|
||||||
|
开放客户自定义算法库:支持客户上传自研算法,兼容 Python/MATLAB/C/C++ 多语言,算法按 “语言 - 名称 - 版本” 分类展示,支持搜索、筛选。
|
||||||
|
黑盒化封装
|
||||||
|
所有算法(标准 / 自研、任意语言)均封装为标准化黑盒插件,对内保留完整算法逻辑,对外仅暴露基于标准化中间数据的输入输出接口。
|
||||||
|
系统自动完成算法的黑盒化处理,客户无需做任何封装、编译、转换操作。
|
||||||
|
版本化管理
|
||||||
|
支持算法多版本保存,自动按 “V + 数字” 打标版本号,也支持客户自定义版本号与版本备注。
|
||||||
|
支持算法版本一键切换、回滚,可选择任意版本作为当前调用版本,无需重新配置接口。
|
||||||
|
支持两个版本算法的效果对比:用同一组测试数据运行,自动生成维度、核心指标、可视化结果的对比报告。
|
||||||
|
保留所有版本的修改日志(修改人、修改时间、备注),支持版本溯源与无用版本轻量删除。
|
||||||
|
2.2.2 接口配置模块
|
||||||
|
双接口模式
|
||||||
|
通用接口模式:预设水声行业标准输入输出字段(如输入:清洗后数据 + 采样率;输出:特征数组 + 信噪比),一键调用,无需配置,适配 80% 常规场景。
|
||||||
|
自定义接口模式:支持复杂接口零代码配置,适配科研试错的个性化需求。
|
||||||
|
零代码配置能力
|
||||||
|
支持通过下拉选择、字段拖拽、多字段组合完成输入 / 输出接口与标准化中间数据的映射。
|
||||||
|
支持为映射字段添加轻量预处理,内置预处理算子库(维度展平 / 取维 / 切片、类型转换、归一化 / 标准化、空值过滤、平滑滤波等),一键选择即可应用。
|
||||||
|
支持预处理规则保存为个人模板,后续配置可直接复用。
|
||||||
|
实时强制校验
|
||||||
|
配置过程中实时完成5 类核心校验,配错即时弹窗提示 + 红色高亮错误位置,错误配置无法保存:
|
||||||
|
① 字段存在性校验:校验映射字段是否在标准化中间数据中存在 / 由前序算法生成;
|
||||||
|
② 数据类型校验:校验字段类型与算法输入要求是否匹配;
|
||||||
|
③ 数据维度校验:检测字段维度是否符合算法常规要求,不匹配则推荐预处理方案;
|
||||||
|
④ 参数完整性校验:多参数映射时校验是否漏配,高亮未配置参数;
|
||||||
|
⑤ 跨语言兼容性校验:配置非 Python 算法时,校验数据跨语言转换的兼容性。
|
||||||
|
接口模板管理
|
||||||
|
配置完成的通用 / 自定义接口均可保存为接口模板,标注模板名称与适用场景,后续调用算法可直接绑定模板,无需重新配置。
|
||||||
|
2.2.3 算法调度模块
|
||||||
|
可视化流程组合
|
||||||
|
支持 Web 界面鼠标拖拽排序,自由组合官方标准算法与客户自研算法,支持 Python/MATLAB/C/C++ 跨语言混编。
|
||||||
|
组合完成后系统自动做流程全局校验,校验前序算法输出与后序算法输入的接口衔接性,不匹配则即时提示并给出修复建议。
|
||||||
|
文字指令智能调度
|
||||||
|
支持客户输入自然语言指令(如 “检测海试声呐数据质量”“浅海低信噪比数据降噪 + STFT 特征提取”),系统自动解析意图、匹配场景算法,生成并执行调度流程。
|
||||||
|
自动化调度执行
|
||||||
|
按客户设定的顺序自动调度算法黑盒,全程完成标准化数据→语言专属数据→标准化数据的双向转换,客户无感知。
|
||||||
|
支持非依赖型算法并行调度,提升大体积数据处理效率。
|
||||||
|
执行过程中 Web 界面实时展示进度条 + 执行日志,显示当前执行算法、处理进度、耗时等信息。
|
||||||
|
流程模板管理
|
||||||
|
组合完成的算法调度流程可保存为流程模板(如 “海试数据质量检测流程”“浅海算法处理流程”),支持模板一键加载、修改、删除。
|
||||||
|
2.2.4 数据处理模块
|
||||||
|
标准化中间数据管理
|
||||||
|
定义WaterSoundData为唯一数据交换标准,包含四大核心字段,支撑全流程数据流转:
|
||||||
|
① 基础原始数据:原始声呐数据、采样率、采集时间、海洋环境参数等;
|
||||||
|
② 处理过程数据:清洗后数据、降噪后数据、时频数据、特征数组等中间结果;
|
||||||
|
③ 结果数据:最终处理结果、核心指标、可视化数据等;
|
||||||
|
④ 元信息:数据状态、执行日志、错误信息、算法版本记录等。
|
||||||
|
多语言数据转换
|
||||||
|
由语言专属桥接插件完成无损数据转换,优先采用内存级传递(内存直接映射 / 指针传递)保证高性能,失败则采用文件级兜底(.npy/.mat/.bin 水声专属轻量化格式)。
|
||||||
|
转换过程中自动完成数据类型、维度的适配,保证数据值与结构不丢失。
|
||||||
|
大体积数据处理
|
||||||
|
支持大体积水声数据自动分块处理,按水声数据特性拆分数据,逐块处理后自动合并结果,避免内存 / CPU 超限。
|
||||||
|
分块规则可由客户在 Web 界面简单配置(如分块大小、合并方式),也可由系统自动适配。
|
||||||
|
2.2.5 容错溯源模块
|
||||||
|
三层递进容错体系
|
||||||
|
配置阶段:实时校验 + 强制拦截,从根源拦截 90% 接口配置错误;
|
||||||
|
转换阶段:自动兜底适配,对维度 / 类型不兼容、空值 / 无穷值、跨语言数据丢失等问题自动修复,修复过程记录日志;
|
||||||
|
执行阶段:隔离式容错 + 降级执行,单个算法出错立即隔离,不影响整体流程;核心算法出错则自动调用同功能官方标准算法兜底执行,保证核心功能不丢失。
|
||||||
|
可视化异常处理
|
||||||
|
所有错误在 Web 界面异常中心集中展示,给出人话提示 + 一键修复建议,无需专业知识即可理解问题根源。
|
||||||
|
支持异常快速定位,点击异常信息可直接跳转到对应的配置 / 执行页面,方便客户修改。
|
||||||
|
全流程溯源
|
||||||
|
自动生成结构化溯源日志,记录从接口配置→数据转换→算法执行→结果输出的每一步操作、数据变化、容错处理,按时间顺序排列,高亮异常步骤。
|
||||||
|
自动生成接口配置校验报告,包含配置信息、校验结果、适配建议,支持日志与报告的在线查看、一键导出(JSON/Excel 格式)。
|
||||||
|
错误修改便捷性
|
||||||
|
接口配置与算法解耦,修改算法代码 / 参数无需重新配置接口,上传新文件即生效;
|
||||||
|
所有修改操作均在原配置 / 执行界面完成,无需跳转其他页面,改完即保存、重新执行。
|
||||||
|
2.2.6 客户扩展模块
|
||||||
|
多语言算法上传
|
||||||
|
支持客户上传各语言算法原始文件,操作极简,无需额外处理:
|
||||||
|
① Python:上传.py 文件 / 直接粘贴函数代码;
|
||||||
|
② MATLAB:上传.m 文件 / 直接粘贴函数代码;
|
||||||
|
③ C/C++:上传单个.c/.cpp 文件 / 多文件压缩包,仅需填写核心函数名。
|
||||||
|
系统自动完成 C/C++ 算法的编译(生成跨平台动态链接库)、所有语言算法的黑盒化封装,客户无感知。
|
||||||
|
两种扩展方式
|
||||||
|
零代码表单式(推荐,适配非开发岗):全程 Web 界面选择、配置,无需写任何代码;
|
||||||
|
极简代码式(适配开发岗):提供封装好的扩展函数,5 行代码即可完成自研算法的注册与适配。
|
||||||
|
轻量化环境适配
|
||||||
|
若客户电脑无 C/C++ 编译器 / MATLAB 引擎,系统给出一键安装引导(轻量版 MinGW,50M 左右;oct2py 开源库),无需配置环境变量。
|
||||||
|
技术支持入口
|
||||||
|
Web 界面内置技术支持入口,客户可一键导出错误日志 + 配置报告,直接发送给厂家,方便快速定位问题。
|
||||||
|
2.2.7 海试专属模块
|
||||||
|
一键质量检测
|
||||||
|
内置海试数据质量检测专属算法组合,支持客户上传海试原始数据,点击 “一键检测” 秒级出初步结果,几分钟内生成完整检测报告。
|
||||||
|
简洁直观的检测报告
|
||||||
|
报告包含核心检测指标(数据完整性、信噪比、数据噪声水平、有效数据占比)、异常点定位、检测结论(合格 / 不合格)、处理建议,支持报告导出(PDF/Excel 格式),适配现场汇报需求。
|
||||||
|
轻量化操作优化
|
||||||
|
海试模式下隐藏复杂配置功能,仅保留 “数据上传→一键检测→报告导出” 核心操作,界面简洁,非专业人员可快速上手。
|
||||||
|
离线快速运行
|
||||||
|
海试模式下系统自动优化资源占用,关闭非核心功能,保证在低配笔记本上也能快速、稳定运行,无卡顿、无崩溃。
|
||||||
|
2.3 非功能需求
|
||||||
|
2.3.1 性能需求
|
||||||
|
部署性能:支持 Windows/Linux/macOS 跨平台运行,单实例运行内存占用≤4GB,CPU 占用可控;客户侧仅需 Python 运行环境,无需 GPU / 高端服务器,零硬件成本。
|
||||||
|
执行性能:
|
||||||
|
C/C++ 算法调用性能与原生编译执行持平,数据转换无额外损耗;
|
||||||
|
海试数据质量检测秒级出初步结果,1GB 以内数据完整处理时间≤5 分钟;
|
||||||
|
支持单文件≥10GB 的大体积数据分块处理,分块处理效率与单块处理持平。
|
||||||
|
响应性能:Web 界面操作响应时间≤1s,接口配置实时校验响应时间≤0.5s,算法调度流程启动响应时间≤2s。
|
||||||
|
2.3.2 稳定性与容错性需求
|
||||||
|
整体容错率≥95%,其中接口配置准确率≥90%,执行异常处理率≥98%,结果准确性≥99%;
|
||||||
|
支持 7×24h 连续运行,无卡死、无崩溃,可处理异常数据、复杂接口、跨语言混编等各类场景;
|
||||||
|
任意算法 / 步骤执行失败,均有兜底方案,保证流程不中断、核心结果可输出;
|
||||||
|
跨语言调用失败率≤1%,失败后自动切换兜底方案,无感知恢复。
|
||||||
|
2.3.3 易用性需求
|
||||||
|
全流程 Web 界面可视化操作,零代码 / 极简代码,科研人员 / 海试操作人员 10 分钟即可上手;
|
||||||
|
错误提示为自然语言人话提示,避免专业技术术语,同时给出明确的修复建议;
|
||||||
|
所有常用功能(算法上传、接口配置、流程组合)均有操作引导弹窗 / 图文帮助,支持一键关闭;
|
||||||
|
界面布局简洁,按 “功能模块” 分栏,核心操作(如一键检测、一键执行)突出显示,适配客户操作习惯。
|
||||||
|
2.3.4 安全性与兼容性需求
|
||||||
|
离线安全性:全程本地离线运行,无网络传输,符合水声行业涉密数据管理要求,不收集、不存储客户任何算法与数据;
|
||||||
|
数据安全性:客户算法与数据均存储在本地,系统仅做临时缓存,执行完成后可一键清理缓存,无数据泄露风险;
|
||||||
|
环境兼容性:支持 Python3.8 及以上版本,兼容 MATLAB R2018 及以上版本,C/C++ 编译器兼容 MinGW/GCC/MSVC;
|
||||||
|
格式兼容性:支持水声行业常用数据格式(.mat/.npy/.bin/.txt),输出结果可导出为 PDF/Excel/JSON 等通用格式。
|
||||||
|
2.3.5 可维护性与可扩展性需求
|
||||||
|
模块化设计:所有功能模块为可插拔式设计,开发、升级、维护互不影响;
|
||||||
|
易开发维护:基于 Python 生态开发,代码简洁、可读性强,2-3 人团队可快速完成维护与迭代;
|
||||||
|
易扩展:支持新增算法语言(如 Fortran/Julia)、新增预处理算子、新增容错规则,仅需开发对应轻量插件,无需重构核心代码;
|
||||||
|
版本迭代:支持产品小版本热更新,客户无需重新安装,仅需替换插件 / 脚本即可完成升级。
|
||||||
|
三、运行环境需求
|
||||||
|
3.1 开发环境
|
||||||
|
操作系统:Windows/Linux/macOS 均可;
|
||||||
|
开发语言:Python3.8+,C/C++(仅用于跨语言桥接);
|
||||||
|
核心依赖:NumPy、SciPy、Streamlit、matlab.engine、oct2py、ctypes、cffi 等。
|
||||||
|
3.2 客户运行环境
|
||||||
|
3.2.1 基础环境
|
||||||
|
操作系统:Windows 10/11、Linux(Ubuntu 18.04+)、macOS 10.15+;
|
||||||
|
核心依赖:Python3.8+(一键安装,无需配置环境变量);
|
||||||
|
硬件配置:CPU≥4 核,内存≥8GB,硬盘剩余空间≥10GB(低配笔记本 / 办公电脑即可满足)。
|
||||||
|
3.2.2 可选环境(按需自动安装)
|
||||||
|
C/C++ 编译器:MinGW/GCC(轻量版,50M 左右,系统一键引导安装);
|
||||||
|
MATLAB 环境:MATLAB R2018+(可选,无则自动切换 oct2py 开源库兜底)。
|
||||||
|
四、验收标准
|
||||||
|
4.1 功能验收
|
||||||
|
所有核心功能模块按需求实现,无功能缺失,操作流程与需求一致;
|
||||||
|
多语言算法(Python/MATLAB/C/C++)均可正常上传、黑盒化、调用,跨语言混编组合流程可正常执行;
|
||||||
|
复杂接口配置可完成所有个性化需求,实时校验准确,错误配置无法保存;
|
||||||
|
容错体系有效,单个算法出错可隔离、核心算法可兜底,流程不中断;
|
||||||
|
海试专属模块可实现一键检测,快速生成符合要求的检测报告。
|
||||||
|
4.2 性能验收
|
||||||
|
满足所有性能需求,部署在低配办公电脑上无明显卡顿,大体积数据可正常分块处理;
|
||||||
|
C/C++ 算法调用性能与原生编译执行持平,数据转换无明显损耗;
|
||||||
|
海试数据质量检测 1GB 以内数据完整处理时间≤5 分钟,秒级出初步结果;
|
||||||
|
Web 界面操作响应时间≤1s,无延迟、无卡死。
|
||||||
|
4.3 稳定性验收
|
||||||
|
连续 72h 运行测试,无崩溃、无卡死,资源占用稳定;
|
||||||
|
随机输入异常数据、配置复杂接口、进行跨语言混编,系统均可正常处理,容错率≥95%;
|
||||||
|
任意算法执行失败,系统均可实现隔离或兜底,核心结果可正常输出。
|
||||||
|
4.4 易用性验收
|
||||||
|
非专业人员(无工程开发知识)按操作引导,10 分钟内可完成算法上传、接口配置、流程组合、执行的完整操作;
|
||||||
|
错误提示清晰,修复建议明确,客户可自主完成 90% 以上的错误修改;
|
||||||
|
所有常用功能均有操作引导,界面布局简洁,操作逻辑符合客户使用习惯。
|
||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
# 🌊 水声算法全协议集成与调度平台 项目审查报告
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、项目概况
|
||||||
|
|
||||||
|
| 项目 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| **项目名称** | 水声算法全协议集成与调度平台 |
|
||||||
|
| **目标客户** | 高校水声实验室、研究所、海试现场测试团队 |
|
||||||
|
| **团队规模** | 2-3 人,核心技术栈 Python |
|
||||||
|
| **当前状态** | Phase 1 已完成(核心框架),Phase 2-5 待开发 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、项目目标
|
||||||
|
|
||||||
|
| 目标类型 | 描述 |
|
||||||
|
|----------|------|
|
||||||
|
| **功能目标** | 多语言算法统一纳管、零代码接口适配、跨语言混编调度、全流程容错溯源、海试数据质量检测 |
|
||||||
|
| **性能目标** | 轻量化离线部署、1GB 数据 ≤5 分钟、容错率 ≥95%、C/C++ 原生性能 |
|
||||||
|
| **体验目标** | Web 可视化、10 分钟上手、人话错误提示、一键修复 |
|
||||||
|
| **商业目标** | 快速落地、预留工业级拓展接口 (C++/Qt) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、系统架构(五层设计)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 用户交互层 (Streamlit) │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 核心调度层 (CoreScheduler) │
|
||||||
|
│ 流程调度 | 实时校验 | 容错处理 | 日志记录 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 桥接插件层 (Bridges) │
|
||||||
|
│ PythonBridge | MATLABBridge | CppBridge │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 算法层 (标准库 + 自定义库) │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 数据层 (WaterSoundData) │
|
||||||
|
│ 原始数据 | 过程数据 | 结果数据 | 元信息 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、已完成模块 (Phase 1)
|
||||||
|
|
||||||
|
| 模块 | 文件 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 数据标准 | `src/models.py` | ✅ 完成 | `WaterSoundData` 统一数据交换格式 |
|
||||||
|
| Python 桥接 | `src/bridges.py` | ✅ 完成 | `AlgorithmBridge` 抽象类 + `PythonBridge` 实现 |
|
||||||
|
| 核心调度 | `src/scheduler.py` | ✅ 完成 | `CoreScheduler` 流程执行与日志记录 |
|
||||||
|
| Web 界面 | `main.py` | ✅ 完成 | Streamlit 首页 + 算法调度演示页 |
|
||||||
|
| 集成测试 | `tests/test_core.py` | ✅ 通过 | 验证多算法链式调用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、技术风险分析与应对策略
|
||||||
|
|
||||||
|
### 5.1 核心风险点
|
||||||
|
|
||||||
|
| 风险 | 级别 | 分析 | 应对策略 |
|
||||||
|
|------|------|------|----------|
|
||||||
|
| **C++ 编译器环境** | 🔴 高 | Windows 上 MinGW 路径配置是非开发人员的噩梦 | **优雅降级**:无编译器时禁用 C++ 上传功能,而非强行引导安装 |
|
||||||
|
| **Streamlit 拖拽限制** | 🟡 中 | 原生不支持复杂连线 | **列表式调度**:先用上下移动排序,跑通再考虑 Canvas 可视化 |
|
||||||
|
| **MATLAB Octave 差异** | 🟡 中 | oct2py 对复杂 Toolbox 支持有限 | **文档说明**:推荐 MATLAB,兼容模式注明精度差异 |
|
||||||
|
| **大数据内存** | 🟢 低 | 10GB 单文件场景少见,海试数据多分段采集 | **后期优化**:Phase 4 再引入 memmap |
|
||||||
|
|
||||||
|
### 5.2 设计原则
|
||||||
|
|
||||||
|
> **先让系统跑起来,再让系统跑得好。**
|
||||||
|
|
||||||
|
避免过早优化,聚焦最小可用版本 (MVP)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、修订后实施计划
|
||||||
|
|
||||||
|
### Phase 2:算法库与接口配置(预计 5 天)— **MVP 聚焦**
|
||||||
|
|
||||||
|
| 任务 | 描述 | 产出物 | 优先级 |
|
||||||
|
|------|------|--------|--------|
|
||||||
|
| 2.1 算法上传 | Python 文件上传、版本自动打标 | `src/algo_library.py` | P0 |
|
||||||
|
| 2.2 元数据存储 | JSON 持久化算法信息 | `data/algorithms.json` | P0 |
|
||||||
|
| 2.3 接口映射 | 下拉选择字段、简单预处理 | `src/interface_config.py` | P0 |
|
||||||
|
| 2.4 实时校验 | 5 类校验规则实现 | `src/validators.py` | P0 |
|
||||||
|
| 2.5 流程排序 | 列表式上下移动(不做拖拽) | 界面更新 | P1 |
|
||||||
|
|
||||||
|
### Phase 3:多语言桥接(预计 4 天)
|
||||||
|
|
||||||
|
| 任务 | 描述 | 产出物 | 优先级 |
|
||||||
|
|------|------|--------|--------|
|
||||||
|
| 3.1 MATLAB 桥接 | matlab.engine + oct2py 兜底 | `src/bridges_matlab.py` | P0 |
|
||||||
|
| 3.2 C++ 编译检测 | 检测 gcc,无则禁用 C++ 功能 | `src/cpp_compiler.py` | P1 |
|
||||||
|
| 3.3 C++ 桥接 | ctypes 加载(仅限有编译器环境) | `src/bridges_cpp.py` | P1 |
|
||||||
|
|
||||||
|
### Phase 4:容错与溯源(预计 3 天)
|
||||||
|
|
||||||
|
| 任务 | 描述 | 产出物 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 4.1 三层容错 | 配置拦截、转换修复、执行兜底 | `src/fault_tolerance.py` |
|
||||||
|
| 4.2 溯源日志 | 结构化日志、时间轴展示 | `src/traceability.py` |
|
||||||
|
| 4.3 异常中心 | 错误列表、一键跳转修复 | 界面页 |
|
||||||
|
| 4.4 大数据优化 | memmap + 分块处理(可选) | 性能优化 |
|
||||||
|
|
||||||
|
### Phase 5:海试专属与打包(预计 3 天)
|
||||||
|
|
||||||
|
| 任务 | 描述 | 产出物 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 5.1 海试模式 | 简化界面、内置质量检测流程 | `src/seatrial_mode.py` |
|
||||||
|
| 5.2 报告生成 | PDF/Excel 导出 | `src/report_generator.py` |
|
||||||
|
| 5.3 产品打包 | PyInstaller 生成可执行文件 | 交付包 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、里程碑时间线(总计 15 天 — 海音·智调 1.0 版本)
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 1: Phase 2 (算法库 + 接口配置) — MVP 核心
|
||||||
|
Week 2: Phase 3 (MATLAB + C++ 桥接) — 多语言支持
|
||||||
|
Week 3: Phase 4 + 5 (容错 + 海试 + 打包) — 产品化
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、关键决策记录
|
||||||
|
|
||||||
|
| 决策 | 理由 | 日期 |
|
||||||
|
|------|------|------|
|
||||||
|
| 暂定全称为:水声算法全协议集成与调度平台 | 回归技术本质,强调“全协议”兼容性,品牌名称推迟至发布前确定 | 2026-01-20 |
|
||||||
|
| 采用列表式调度而非拖拽连线 | Streamlit 原生限制 + 开发成本 | 2026-01-20 |
|
||||||
|
| C++ 环境采用"优雅降级"策略 | 避免非开发人员配置编译器的门槛 | 2026-01-20 |
|
||||||
|
| 大数据 memmap 优化推迟至 Phase 4 | 目标场景以分段数据为主,10GB 单文件少见 | 2026-01-20 |
|
||||||
|
| 算法能力标签系统简化为"类型下拉" | 降低上传门槛,后期再细化 | 2026-01-20 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*报告生成时间:2026-01-20*
|
||||||
|
*最后更新:2026-01-20 20:19*
|
||||||
@@ -0,0 +1,624 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import numpy as np
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Ensure src is in path
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from src.models import WaterSoundData
|
||||||
|
from src.bridges import PythonBridge
|
||||||
|
from src.scheduler import CoreScheduler
|
||||||
|
from src.algo_loader import load_algo_func
|
||||||
|
|
||||||
|
st.set_page_config(page_title="水声算法全协议集成与调度平台", layout="wide")
|
||||||
|
|
||||||
|
from src.algo_manager import AlgorithmManager
|
||||||
|
from src.template_manager import TemplateManager
|
||||||
|
|
||||||
|
ALGO_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/algorithms.json")
|
||||||
|
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/templates")
|
||||||
|
|
||||||
|
def set_design_system():
|
||||||
|
st.markdown("""
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
/* Keep offline-friendly font stack (Inter if installed). */
|
||||||
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, "Noto Sans", "Liberation Sans", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar Styling */
|
||||||
|
[data-testid="stSidebar"] {
|
||||||
|
background-image: linear-gradient(180deg, #023e8a 0%, #03045e 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
[data-testid="stSidebar"] * {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Metric Cards */
|
||||||
|
[data-testid="stMetricValue"] {
|
||||||
|
font-size: 2.5rem !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: #00b4d8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glassmorphism Containers (Approximation in Streamlit) */
|
||||||
|
.stExpander {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||||
|
background: rgba(255, 255, 255, 0.02) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Badges */
|
||||||
|
.algo-badge {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.badge-python { background-color: #3776AB; }
|
||||||
|
.badge-cpp { background-color: #7C3AED; }
|
||||||
|
.badge-matlab { background-color: #ED8B00; }
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero {
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 15px;
|
||||||
|
background: linear-gradient(90deg, #0077b6 0%, #0096c7 100%);
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.stButton>button {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
border: none !important;
|
||||||
|
background-color: #0077b6 !important;
|
||||||
|
color: white !important;
|
||||||
|
transition: all 0.3s ease !important;
|
||||||
|
}
|
||||||
|
.stButton>button:hover {
|
||||||
|
background-color: #00b4d8 !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
set_design_system()
|
||||||
|
st.sidebar.markdown("<h1 style='text-align: center; color: white;'>🌊 HYD-SCHED</h1>", unsafe_allow_html=True)
|
||||||
|
st.sidebar.markdown("<p style='text-align: center; opacity: 0.8;'>海音达算法智能调度平台 v3.5</p>", unsafe_allow_html=True)
|
||||||
|
st.sidebar.divider()
|
||||||
|
app_mode = st.sidebar.selectbox("功能菜单",
|
||||||
|
["首页", "算法库管理", "接口配置", "算法调度", "海试专属", "异常中心"])
|
||||||
|
|
||||||
|
if app_mode == "首页":
|
||||||
|
show_home()
|
||||||
|
elif app_mode == "算法库管理":
|
||||||
|
show_algo_library()
|
||||||
|
elif app_mode == "接口配置":
|
||||||
|
show_interface_config()
|
||||||
|
elif app_mode == "算法调度":
|
||||||
|
show_scheduling()
|
||||||
|
elif app_mode == "海试专属":
|
||||||
|
show_sea_trial()
|
||||||
|
elif app_mode == "异常中心":
|
||||||
|
show_exceptions()
|
||||||
|
else:
|
||||||
|
st.info(f"模块 '{app_mode}' 正在开发中...")
|
||||||
|
|
||||||
|
# Keep sidebar spacing without relying on non-Streamlit APIs.
|
||||||
|
st.sidebar.markdown("<div style='height: 100px;'></div>", unsafe_allow_html=True)
|
||||||
|
st.sidebar.markdown("""
|
||||||
|
<div style="position: fixed; bottom: 20px; width: 200px; padding: 10px; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
<p style="font-size: 0.7rem; opacity: 0.6; margin: 0;">平台状态: 🟢 已就绪</p>
|
||||||
|
<p style="font-size: 0.7rem; opacity: 0.6; margin: 0;">环境检测: ✅ 2.0.4</p>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
def show_home():
|
||||||
|
st.markdown("""
|
||||||
|
<div class="hero">
|
||||||
|
<h1>🌊 海音达智能调度处理系统</h1>
|
||||||
|
<p style="font-size: 1.2rem; opacity: 0.9;">
|
||||||
|
全协议集成 · 五维实时校验 · 跨语言桥接驱动
|
||||||
|
</p>
|
||||||
|
<hr style="border-color: rgba(255,255,255,0.2);">
|
||||||
|
<div style="display: flex; gap: 20px;">
|
||||||
|
<span>✅ Python Native</span>
|
||||||
|
<span>⚡ C++ Performance</span>
|
||||||
|
<span>📊 MATLAB Legacy</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
with col1:
|
||||||
|
st.subheader("🚀 核心能力")
|
||||||
|
st.markdown("""
|
||||||
|
- **全协议兼容**: 支持 Python/C++/MATLAB/Octave 原生算法无缝调用。
|
||||||
|
- **工业级校验**: 5维字段、类型、空间、完整性与环境兼容性实时监测。
|
||||||
|
- **动态资产化**: 算法元数据驱动,支持 I/O 映射模板化管理。
|
||||||
|
""")
|
||||||
|
with col2:
|
||||||
|
st.subheader("⚓ 应用场景")
|
||||||
|
st.markdown("""
|
||||||
|
- **海试一键巡检**: 自动化质量巡检链路,实时出具测评分析报表。
|
||||||
|
- **多源异构调度**: 将不同语言编写的声呐算法快速编排入链。
|
||||||
|
- **异常深度追溯**: 聚合校验、执行与环境日志,一键打包现场问题。
|
||||||
|
""")
|
||||||
|
|
||||||
|
def show_algo_library():
|
||||||
|
st.title("📚 算法库管理")
|
||||||
|
|
||||||
|
from src.env_detect import collect_env_info, get_available_algo_types
|
||||||
|
|
||||||
|
manager = AlgorithmManager(ALGO_DB)
|
||||||
|
algos = manager.load_algorithms()
|
||||||
|
env = collect_env_info()
|
||||||
|
available_types = get_available_algo_types()
|
||||||
|
|
||||||
|
# 环境状态概览
|
||||||
|
with st.expander("🖥️ 当前环境能力", expanded=False):
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
col1.metric("Python", "✅ 可用")
|
||||||
|
col2.metric("MATLAB/Octave", "✅ 可用" if "matlab" in available_types else "❌ 不可用")
|
||||||
|
col3.metric("C++ 编译器", "✅ 可用" if "cpp" in available_types else "❌ 不可用")
|
||||||
|
st.json(env)
|
||||||
|
|
||||||
|
st.subheader("已注册算法")
|
||||||
|
|
||||||
|
# 筛选器
|
||||||
|
filter_type = st.selectbox("按类型筛选", ["全部"] + available_types)
|
||||||
|
|
||||||
|
for algo in algos:
|
||||||
|
algo_type = algo.get("type", "python")
|
||||||
|
if filter_type != "全部" and algo_type != filter_type:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 判断是否可用
|
||||||
|
is_available = algo_type in available_types
|
||||||
|
status_icon = "🔵" if is_available else "⚪"
|
||||||
|
badge_class = f"badge-{algo_type}"
|
||||||
|
|
||||||
|
with st.expander(f"{status_icon} {algo['name']} (v{algo['version']})"):
|
||||||
|
st.markdown(f'<div class="algo-badge {badge_class}">{algo_type.upper()}</div>', unsafe_allow_html=True)
|
||||||
|
st.write(f"**描述**: {algo.get('description', 'N/A')}")
|
||||||
|
if algo_type == "python":
|
||||||
|
st.write(f"**入口**: `{algo.get('module', 'N/A')}.{algo.get('function', 'N/A')}`")
|
||||||
|
elif algo_type == "cpp":
|
||||||
|
st.write(f"**入口**: `{algo.get('lib_path', 'N/A')}::{algo.get('function', 'N/A')}`")
|
||||||
|
elif algo_type == "matlab":
|
||||||
|
st.write(f"**入口**: `{algo.get('m_path', 'N/A')}::{algo.get('function', 'N/A')}`")
|
||||||
|
else:
|
||||||
|
st.write(f"**入口**: `{algo.get('function', 'N/A')}`")
|
||||||
|
|
||||||
|
st.write("**输入参数**:")
|
||||||
|
for k, v in (algo.get("inputs") or {}).items():
|
||||||
|
st.write(f" - `{k}`: {v.get('type', '?')} → `{v.get('field', k)}`")
|
||||||
|
|
||||||
|
st.write("**输出参数**:")
|
||||||
|
for k, v in (algo.get("outputs") or {}).items():
|
||||||
|
st.write(f" - `{k}`: {v.get('type', '?')}")
|
||||||
|
|
||||||
|
if not is_available:
|
||||||
|
st.warning(f"⚠️ 当前环境不支持 {algo_type} 类型算法,请安装相应依赖。")
|
||||||
|
|
||||||
|
def show_interface_config():
|
||||||
|
st.title("🔧 接口配置")
|
||||||
|
|
||||||
|
template_manager = TemplateManager(TEMPLATE_DIR)
|
||||||
|
manager = AlgorithmManager(ALGO_DB)
|
||||||
|
algos = manager.load_algorithms()
|
||||||
|
|
||||||
|
st.subheader("创建/编辑接口模板")
|
||||||
|
|
||||||
|
algo_names = [a["name"] for a in algos]
|
||||||
|
selected_algo = st.selectbox("选择算法", algo_names) if algo_names else None
|
||||||
|
|
||||||
|
if selected_algo:
|
||||||
|
algo = manager.get_algorithm(selected_algo)
|
||||||
|
|
||||||
|
st.write("**输入映射配置**")
|
||||||
|
wsd_fields = ["raw_data", "sample_rate", "collect_time", "sea_env",
|
||||||
|
"clean_data", "denoised_data", "stft_data", "feat_data",
|
||||||
|
"final_result", "core_index", "vis_data"]
|
||||||
|
|
||||||
|
input_mapping = {}
|
||||||
|
for param, spec in (algo.get("inputs") or {}).items():
|
||||||
|
default_field = (spec or {}).get("field", param)
|
||||||
|
selected_field = st.selectbox(
|
||||||
|
f"`{param}` 映射到",
|
||||||
|
wsd_fields,
|
||||||
|
index=wsd_fields.index(default_field) if default_field in wsd_fields else 0,
|
||||||
|
key=f"in_{param}"
|
||||||
|
)
|
||||||
|
input_mapping[param] = selected_field
|
||||||
|
|
||||||
|
st.write("**输出映射配置**")
|
||||||
|
output_mapping = {}
|
||||||
|
for field, spec in (algo.get("outputs") or {}).items():
|
||||||
|
idx = (spec or {}).get("index", 0)
|
||||||
|
selected_field = st.selectbox(
|
||||||
|
f"输出 `{field}` 写入",
|
||||||
|
wsd_fields,
|
||||||
|
index=wsd_fields.index(field) if field in wsd_fields else 0,
|
||||||
|
key=f"out_{field}"
|
||||||
|
)
|
||||||
|
output_mapping[str(idx)] = selected_field
|
||||||
|
|
||||||
|
st.write("**预处理算子(可选)**")
|
||||||
|
preprocessing = []
|
||||||
|
if st.checkbox("启用归一化 (normalize)"):
|
||||||
|
preprocessing.append({"op": "normalize", "field": list(input_mapping.values())[0] if input_mapping else "raw_data", "params": {"min": 0, "max": 1}})
|
||||||
|
if st.checkbox("启用缩放 (scale)"):
|
||||||
|
factor = st.number_input("缩放因子", value=1.0, step=0.1)
|
||||||
|
preprocessing.append({"op": "scale", "field": list(input_mapping.values())[0] if input_mapping else "raw_data", "params": {"factor": factor}})
|
||||||
|
|
||||||
|
template_name = st.text_input("模板名称", value=selected_algo)
|
||||||
|
|
||||||
|
if st.button("保存模板"):
|
||||||
|
template_data = {
|
||||||
|
"algorithm": selected_algo,
|
||||||
|
"input_mapping": input_mapping,
|
||||||
|
"output_mapping": output_mapping,
|
||||||
|
"preprocessing": preprocessing,
|
||||||
|
}
|
||||||
|
template_manager.save_template(template_name, template_data)
|
||||||
|
st.success(f"✅ 模板 '{template_name}' 已保存!")
|
||||||
|
|
||||||
|
st.divider()
|
||||||
|
st.subheader("已保存模板")
|
||||||
|
templates = template_manager.list_templates()
|
||||||
|
if templates:
|
||||||
|
for tpl_name in templates:
|
||||||
|
with st.expander(f"📄 {tpl_name}"):
|
||||||
|
tpl = template_manager.load_template(tpl_name)
|
||||||
|
st.json(tpl)
|
||||||
|
else:
|
||||||
|
st.info("暂无已保存模板。")
|
||||||
|
|
||||||
|
def show_scheduling():
|
||||||
|
st.title("⚙️ 算法链路编排与调度")
|
||||||
|
|
||||||
|
manager = AlgorithmManager(ALGO_DB)
|
||||||
|
available_algos = manager.load_algorithms()
|
||||||
|
template_manager = TemplateManager(TEMPLATE_DIR)
|
||||||
|
|
||||||
|
if 'pipeline' not in st.session_state:
|
||||||
|
st.session_state.pipeline = []
|
||||||
|
|
||||||
|
col1, col2 = st.columns([1, 1])
|
||||||
|
|
||||||
|
# Visual Pipeline flow
|
||||||
|
if st.session_state.pipeline:
|
||||||
|
st.write("**📦 运行流水线预览**")
|
||||||
|
flow_html = '<div style="display: flex; align-items: center; gap: 10px; overflow-x: auto; padding: 10px;">'
|
||||||
|
for i, algo in enumerate(st.session_state.pipeline):
|
||||||
|
algo_type = algo.get("type", "python")
|
||||||
|
badge_class = f"badge-{algo_type}"
|
||||||
|
flow_html += f'''
|
||||||
|
<div style="min-width: 120px; padding: 10px; border: 1px solid #0077b6; border-radius: 8px; text-align: center; background: rgba(0,119,182,0.05);">
|
||||||
|
<div style="font-size: 0.7rem; opacity: 0.7;">STEP {i+1}</div>
|
||||||
|
<div style="font-weight: 600; font-size: 0.9rem;">{algo["name"]}</div>
|
||||||
|
<div class="algo-badge {badge_class}" style="margin: 5px 0 0 0; font-size: 0.6rem;">{algo_type.upper()}</div>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
if i < len(st.session_state.pipeline) - 1:
|
||||||
|
flow_html += '<div style="font-size: 1.5rem; color: #0077b6;">➡</div>'
|
||||||
|
flow_html += '</div>'
|
||||||
|
st.markdown(flow_html, unsafe_allow_html=True)
|
||||||
|
st.divider()
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("1. 链路编排")
|
||||||
|
if 'pipeline' not in st.session_state:
|
||||||
|
st.session_state.pipeline = []
|
||||||
|
|
||||||
|
algo_names = sorted({a["name"] for a in available_algos}) if available_algos else []
|
||||||
|
if not algo_names:
|
||||||
|
st.warning("算法库为空:请先在 algorithms.json 注册算法元数据。")
|
||||||
|
return
|
||||||
|
|
||||||
|
selected_algo_name = st.selectbox("选择算法添加至链路", algo_names)
|
||||||
|
if st.button("添加至链路"):
|
||||||
|
algo_meta = manager.get_algorithm(selected_algo_name)
|
||||||
|
st.session_state.pipeline.append(algo_meta)
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
# 链路列表与移动操作
|
||||||
|
for i, algo in enumerate(st.session_state.pipeline):
|
||||||
|
cols = st.columns([0.6, 0.1, 0.1, 0.2])
|
||||||
|
cols[0].write(f"{i+1}. **{algo['name']}** (v{algo['version']})")
|
||||||
|
if cols[1].button("↑", key=f"up_{i}") and i > 0:
|
||||||
|
st.session_state.pipeline[i], st.session_state.pipeline[i-1] = st.session_state.pipeline[i-1], st.session_state.pipeline[i]
|
||||||
|
st.rerun()
|
||||||
|
if cols[2].button("↓", key=f"down_{i}") and i < len(st.session_state.pipeline) - 1:
|
||||||
|
st.session_state.pipeline[i], st.session_state.pipeline[i+1] = st.session_state.pipeline[i+1], st.session_state.pipeline[i]
|
||||||
|
st.rerun()
|
||||||
|
if cols[3].button("移除", key=f"rm_{i}"):
|
||||||
|
st.session_state.pipeline.pop(i)
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("2. 数据与校验")
|
||||||
|
sample_rate = st.number_input("采样率 (Hz)", min_value=1.0, value=1000.0, step=100.0)
|
||||||
|
uploaded_file = st.file_uploader("上传待处理数据 (.npy)", type=["npy"])
|
||||||
|
auto_apply_template = st.checkbox("自动应用同名接口模板(若存在)", value=True)
|
||||||
|
|
||||||
|
if uploaded_file and st.session_state.pipeline:
|
||||||
|
data = np.load(uploaded_file)
|
||||||
|
wsd = WaterSoundData(raw_data=data, sample_rate=float(sample_rate))
|
||||||
|
|
||||||
|
# 自动映射 (Phase 2 简化版:直接使用元数据中的映射)
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
bridges = []
|
||||||
|
for algo in st.session_state.pipeline:
|
||||||
|
tpl = None
|
||||||
|
if auto_apply_template:
|
||||||
|
try:
|
||||||
|
tpl = template_manager.load_template(algo["name"])
|
||||||
|
except FileNotFoundError:
|
||||||
|
tpl = None
|
||||||
|
|
||||||
|
if tpl:
|
||||||
|
in_map = tpl.get("input_mapping") or {}
|
||||||
|
out_map = tpl.get("output_mapping") or {}
|
||||||
|
preprocessing = tpl.get("preprocessing") or []
|
||||||
|
else:
|
||||||
|
in_map = {
|
||||||
|
param: (spec or {}).get("field") or param
|
||||||
|
for param, spec in (algo.get("inputs") or {}).items()
|
||||||
|
}
|
||||||
|
out_map = {
|
||||||
|
str((spec or {}).get("index", 0)): field
|
||||||
|
for field, spec in (algo.get("outputs") or {}).items()
|
||||||
|
}
|
||||||
|
preprocessing = []
|
||||||
|
|
||||||
|
from src.bridges import PythonBridge, CppBridge
|
||||||
|
algo_type = algo.get("type", "python")
|
||||||
|
|
||||||
|
if algo_type == "python":
|
||||||
|
try:
|
||||||
|
algo_func = load_algo_func(algo.get("module", ""), algo.get("function", ""))
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"算法加载失败:{algo.get('name', '<unknown>')} - {e}")
|
||||||
|
st.stop()
|
||||||
|
bridge = PythonBridge(
|
||||||
|
algo["name"],
|
||||||
|
algo_func,
|
||||||
|
in_map,
|
||||||
|
out_map,
|
||||||
|
preprocessing=preprocessing,
|
||||||
|
requirements=algo.get("inputs") or {},
|
||||||
|
output_specs=algo.get("outputs") or {},
|
||||||
|
algo_type=algo_type,
|
||||||
|
)
|
||||||
|
elif algo_type == "cpp":
|
||||||
|
bridge = CppBridge(
|
||||||
|
algo["name"],
|
||||||
|
algo.get("lib_path", ""),
|
||||||
|
algo.get("function", ""),
|
||||||
|
in_map,
|
||||||
|
out_map,
|
||||||
|
preprocessing=preprocessing,
|
||||||
|
requirements=algo.get("inputs") or {},
|
||||||
|
output_specs=algo.get("outputs") or {},
|
||||||
|
)
|
||||||
|
elif algo_type == "matlab":
|
||||||
|
from src.bridges import MatlabBridge
|
||||||
|
bridge = MatlabBridge(
|
||||||
|
algo["name"],
|
||||||
|
algo.get("m_path", ""),
|
||||||
|
algo.get("function", ""),
|
||||||
|
in_map,
|
||||||
|
out_map,
|
||||||
|
preprocessing=preprocessing,
|
||||||
|
requirements=algo.get("inputs") or {},
|
||||||
|
output_specs=algo.get("outputs") or {},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
st.warning(f"暂不支持的算法类型: {algo_type}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
bridges.append(bridge)
|
||||||
|
|
||||||
|
scheduler.set_flow(bridges)
|
||||||
|
|
||||||
|
if st.button("🔍 链路全局校验"):
|
||||||
|
issues = scheduler.validate_flow(wsd)
|
||||||
|
st.session_state.last_validation_issues = issues
|
||||||
|
if not issues:
|
||||||
|
st.success("✅ 校验通过:所有字段、类型与维度匹配。")
|
||||||
|
else:
|
||||||
|
for issue in issues:
|
||||||
|
st.error(f"❌ {issue['algo']}: {issue['message']}")
|
||||||
|
st.info(issue['suggestion'])
|
||||||
|
|
||||||
|
if st.button("🚀 一键执行调度"):
|
||||||
|
issues = scheduler.validate_flow(wsd)
|
||||||
|
st.session_state.last_validation_issues = issues
|
||||||
|
if issues:
|
||||||
|
st.error("⚠️ 校验未通过:已拦截执行,请先修复问题。")
|
||||||
|
for issue in issues:
|
||||||
|
st.error(f"❌ {issue['algo']}: {issue['message']}")
|
||||||
|
st.info(issue['suggestion'])
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
with st.spinner("流水线执行中..."):
|
||||||
|
result_wsd = scheduler.execute(wsd)
|
||||||
|
st.session_state.last_run = {
|
||||||
|
"status": result_wsd.status,
|
||||||
|
"error_info": result_wsd.error_info,
|
||||||
|
"log": result_wsd.log,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result_wsd.status == "finished":
|
||||||
|
st.success("🎉 执行成功")
|
||||||
|
else:
|
||||||
|
st.error(f"⚠️ 执行中断: {result_wsd.error_info}")
|
||||||
|
|
||||||
|
with st.expander("实时执行日志"):
|
||||||
|
st.table(result_wsd.log)
|
||||||
|
|
||||||
|
def show_sea_trial():
|
||||||
|
st.title("⛴️ 海试专属一键检测")
|
||||||
|
st.info("内置『一键质量检测』模板:去直流 -> 带通滤波 -> 质量巡检 -> SNR估计")
|
||||||
|
|
||||||
|
sample_rate = st.number_input("采样率 (Hz)", min_value=1.0, value=1000.0, step=100.0)
|
||||||
|
uploaded_file = st.file_uploader("上传海试原始数据 (.npy)", type=["npy"])
|
||||||
|
|
||||||
|
if uploaded_file:
|
||||||
|
data = np.load(uploaded_file)
|
||||||
|
wsd = WaterSoundData(raw_data=data, sample_rate=float(sample_rate))
|
||||||
|
|
||||||
|
# 预设模板执行
|
||||||
|
if st.button("开始一键巡检"):
|
||||||
|
# 这里的逻辑可以抽象为从 TemplateManager 加载一个名叫 "sea_trial_default" 的模板
|
||||||
|
# 这里硬编码模拟一镜到底
|
||||||
|
try:
|
||||||
|
# 1. 加载函数
|
||||||
|
from src.sample_algorithms import dc_removal_and_bandpass, quality_check, snr_estimation
|
||||||
|
|
||||||
|
# 2. 构造链路
|
||||||
|
b1 = PythonBridge("去直流+带通", dc_removal_and_bandpass,
|
||||||
|
{"raw_data": "raw_data", "fs": "sample_rate"}, {"0": "clean_data"})
|
||||||
|
b2 = PythonBridge("质量巡检", quality_check,
|
||||||
|
{"raw_data": "raw_data"}, {"0": "core_index"})
|
||||||
|
b3 = PythonBridge("SNR估计", snr_estimation,
|
||||||
|
{"raw_data": "clean_data"}, {"0": "snr_value"})
|
||||||
|
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([b1, b2, b3])
|
||||||
|
|
||||||
|
with st.spinner("正在巡检中..."):
|
||||||
|
result = scheduler.execute(wsd)
|
||||||
|
st.session_state.last_run = {
|
||||||
|
"status": result.status,
|
||||||
|
"error_info": result.error_info,
|
||||||
|
"log": result.log,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.status == "finished":
|
||||||
|
st.success("✅ 巡检完成")
|
||||||
|
|
||||||
|
# 展示简报 (使用更高级的展示方式)
|
||||||
|
snr_value = getattr(result, "snr_value", None)
|
||||||
|
|
||||||
|
st.markdown("""
|
||||||
|
<div style="background: rgba(0,119,182,0.05); padding: 20px; border-radius: 12px; border: 1px solid rgba(0,119,182,0.2); margin-bottom: 20px;">
|
||||||
|
<h3 style="margin-top: 0; color: #0077b6;">📊 巡检简报</h3>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
c1, c2, c3 = st.columns(3)
|
||||||
|
with c1:
|
||||||
|
snr_str = f"{snr_value:.2f} dB" if isinstance(snr_value, (int, float)) and np.isfinite(snr_value) else "N/A"
|
||||||
|
st.metric("信噪比 (SNR)", snr_str)
|
||||||
|
with c2:
|
||||||
|
st.metric("最大幅值 (Peak)", f"{result.core_index.get('max_abs_val',0):.2f}")
|
||||||
|
with c3:
|
||||||
|
st.metric("质量评估分", f"{result.core_index.get('quality_score',0):.1f}")
|
||||||
|
|
||||||
|
# 导出报告
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
timestamp = result.log[0].get("timestamp") if result.log else ""
|
||||||
|
row = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"sample_rate_hz": float(sample_rate),
|
||||||
|
"snr_db": snr_value,
|
||||||
|
"saturation": result.core_index.get("saturation"),
|
||||||
|
"dropouts": result.core_index.get("dropouts"),
|
||||||
|
"max_abs_val": result.core_index.get("max_abs_val"),
|
||||||
|
"quality_score": result.core_index.get("quality_score"),
|
||||||
|
}
|
||||||
|
output = io.StringIO()
|
||||||
|
writer = csv.DictWriter(output, fieldnames=list(row.keys()))
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerow(row)
|
||||||
|
st.download_button("下载 CSV 报告", output.getvalue(), "sea_trial_report.csv", "text/csv")
|
||||||
|
else:
|
||||||
|
st.error(f"巡检中断:{result.error_info}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"系统错误:{e}")
|
||||||
|
|
||||||
|
def show_exceptions():
|
||||||
|
st.markdown("""
|
||||||
|
<div style="background: rgba(255,107,107,0.1); padding: 1.5rem; border-left: 5px solid #ff6b6b; border-radius: 8px; margin-bottom: 2rem;">
|
||||||
|
<h2 style="margin:0; color: #ff6b6b;">🚑 异常中心 (Exception Hub)</h2>
|
||||||
|
<p style="margin:0; opacity: 0.8;">聚合全链路校验与执行错误,支持一键导出排障包。</p>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
issues = st.session_state.get("last_validation_issues")
|
||||||
|
last_run = st.session_state.get("last_run")
|
||||||
|
|
||||||
|
if issues is None and last_run is None:
|
||||||
|
st.info("💡 暂无异常记录。所有组件目前运行平稳。")
|
||||||
|
return
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("🛡️ 静态校验追踪")
|
||||||
|
if issues is not None:
|
||||||
|
if not issues:
|
||||||
|
st.success("✅ 链路校验完美 (Zero Issues)")
|
||||||
|
else:
|
||||||
|
for issue in issues:
|
||||||
|
with st.container():
|
||||||
|
st.markdown(f"""
|
||||||
|
<div style="padding: 10px; border: 1px solid rgba(255,107,107,0.3); border-radius: 8px; margin-bottom: 10px;">
|
||||||
|
<strong style="color: #ff6b6b;">❌ {issue.get('algo', 'Global')}</strong><br/>
|
||||||
|
<span style="font-size: 0.9rem;">{issue.get('message', '')}</span>
|
||||||
|
<div style="background: rgba(0,0,0,0.05); padding: 5px; margin-top: 5px; font-size: 0.8rem; border-radius: 4px;">
|
||||||
|
💡 修复建议: {issue.get('suggestion', '')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("⚙️ 动态执行追踪")
|
||||||
|
if last_run is not None:
|
||||||
|
status = last_run.get('status')
|
||||||
|
color = "#51cf66" if status == "finished" else "#ff6b6b"
|
||||||
|
st.markdown(f'状态: <span style="color: {color}; font-weight: bold;">{status.upper()}</span>', unsafe_allow_html=True)
|
||||||
|
|
||||||
|
if last_run.get("error_info"):
|
||||||
|
st.error(f"运行时拦截: {last_run.get('error_info')}")
|
||||||
|
|
||||||
|
with st.expander("📝 详细追踪日志"):
|
||||||
|
st.table(last_run.get("log") or [])
|
||||||
|
|
||||||
|
st.divider()
|
||||||
|
c1, c2 = st.columns(2)
|
||||||
|
with c1:
|
||||||
|
with st.expander("🖥️ 环境自检快照"):
|
||||||
|
from src.env_detect import collect_env_info
|
||||||
|
st.json(collect_env_info())
|
||||||
|
with c2:
|
||||||
|
st.write("📦 **故障包存档**")
|
||||||
|
import json
|
||||||
|
payload = {
|
||||||
|
"validation_issues": issues,
|
||||||
|
"last_run": last_run,
|
||||||
|
}
|
||||||
|
st.download_button(
|
||||||
|
"下载排障 JSON 包",
|
||||||
|
json.dumps(payload, ensure_ascii=False, indent=2),
|
||||||
|
file_name="issue_bundle.json",
|
||||||
|
mime="application/json",
|
||||||
|
use_container_width=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
streamlit>=1.28.0
|
||||||
|
numpy>=1.21.0
|
||||||
|
scipy>=1.7.0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="tidal",
|
||||||
|
version="0.1.0",
|
||||||
|
description="水声算法智能调度处理软件 - Acoustic Algorithm Intelligent Scheduling Platform",
|
||||||
|
author="Your Team",
|
||||||
|
python_requires=">=3.8",
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
|
"streamlit>=1.28.0",
|
||||||
|
"numpy>=1.21.0",
|
||||||
|
"scipy>=1.7.0",
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"tidal=main:main",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Tidal - src package init
|
||||||
|
from .models import WaterSoundData
|
||||||
|
from .bridges import AlgorithmBridge, PythonBridge
|
||||||
|
from .scheduler import CoreScheduler
|
||||||
|
|
||||||
|
__all__ = ["WaterSoundData", "AlgorithmBridge", "PythonBridge", "CoreScheduler"]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
|
from importlib import import_module
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=128)
|
||||||
|
def load_algo_func(module_path: str, function_name: str) -> Callable[..., Any]:
|
||||||
|
if not module_path or not function_name:
|
||||||
|
raise ValueError("module_path/function_name 不能为空")
|
||||||
|
|
||||||
|
module = import_module(module_path)
|
||||||
|
func = getattr(module, function_name, None)
|
||||||
|
if func is None:
|
||||||
|
raise AttributeError(f"模块 '{module_path}' 中未找到函数 '{function_name}'")
|
||||||
|
if not callable(func):
|
||||||
|
raise TypeError(f"'{module_path}.{function_name}' 不是可调用对象")
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
|
class AlgorithmManager:
|
||||||
|
"""算法库管理类 (Algorithm Library Management)"""
|
||||||
|
def __init__(self, db_path: str):
|
||||||
|
self.db_path = db_path
|
||||||
|
self._ensure_db()
|
||||||
|
|
||||||
|
def _ensure_db(self):
|
||||||
|
"""确保数据库文件存在"""
|
||||||
|
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
||||||
|
if not os.path.exists(self.db_path):
|
||||||
|
with open(self.db_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump([], f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
def load_algorithms(self) -> List[Dict[str, Any]]:
|
||||||
|
"""加载所有算法元数据"""
|
||||||
|
try:
|
||||||
|
with open(self.db_path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_algorithms(self, algos: List[Dict[str, Any]]):
|
||||||
|
"""保存算法元数据"""
|
||||||
|
with open(self.db_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(algos, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
def register_algorithm(self, metadata: Dict[str, Any]):
|
||||||
|
"""注册新算法或更新现有算法(版本打标)"""
|
||||||
|
algos = self.load_algorithms()
|
||||||
|
# 检查是否已存在同名同版本的算法
|
||||||
|
for i, algo in enumerate(algos):
|
||||||
|
if algo['name'] == metadata['name'] and algo['version'] == metadata['version']:
|
||||||
|
algos[i] = metadata
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
algos.append(metadata)
|
||||||
|
|
||||||
|
self.save_algorithms(algos)
|
||||||
|
|
||||||
|
def get_algorithm(self, name: str, version: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||||||
|
"""获取指定算法及其元数据"""
|
||||||
|
algos = self.load_algorithms()
|
||||||
|
# 如果未指定版本,返回最新版本(这里简化为最后一个匹配项)
|
||||||
|
matches = [a for a in algos if a['name'] == name]
|
||||||
|
if not matches:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if version:
|
||||||
|
for a in matches:
|
||||||
|
if a['version'] == version:
|
||||||
|
return a
|
||||||
|
return matches[-1]
|
||||||
|
|
||||||
|
def list_all(self) -> List[str]:
|
||||||
|
"""列出所有算法名称(不重复)"""
|
||||||
|
algos = self.load_algorithms()
|
||||||
|
return list(set(a['name'] for a in algos))
|
||||||
+286
@@ -0,0 +1,286 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from src.models import WaterSoundData
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from typing import Callable, Dict, List, Any, Optional
|
||||||
|
|
||||||
|
class AlgorithmBridge(ABC):
|
||||||
|
"""桥接插件层抽象基类"""
|
||||||
|
def __init__(self, name: str, is_core: bool = False):
|
||||||
|
self.name = name
|
||||||
|
self.is_core = is_core # 核心算法标志,用于容错策略
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def call_algo(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PythonBridge(AlgorithmBridge):
|
||||||
|
"""Python 桥接插件"""
|
||||||
|
def __init__(self, name: str, algo_func: Callable,
|
||||||
|
input_mapping: Dict[str, str],
|
||||||
|
output_mapping: Dict[str, str],
|
||||||
|
preprocessing: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
is_core: bool = False,
|
||||||
|
requirements: Optional[Dict[str, Any]] = None,
|
||||||
|
output_specs: Optional[Dict[str, Any]] = None,
|
||||||
|
algo_type: str = "python"):
|
||||||
|
super().__init__(name, is_core)
|
||||||
|
self.algo_func = algo_func # Python算法核心函数
|
||||||
|
self.input_mapping = input_mapping # 输入映射 {algo_param: water_data_attr}
|
||||||
|
self.output_mapping = output_mapping# 输出映射 {algo_return_index: water_data_attr}
|
||||||
|
self.preprocessing = preprocessing or [] # 预处理算子 [{ "op": "norm", "params": {...} }]
|
||||||
|
self.requirements = requirements or {}
|
||||||
|
self.output_specs = output_specs or {}
|
||||||
|
self.algo_type = algo_type
|
||||||
|
|
||||||
|
def _apply_preprocessing(self, data: Any, ops: List[Dict[str, Any]]) -> Any:
|
||||||
|
"""应用轻量级预处理算子"""
|
||||||
|
import numpy as np
|
||||||
|
for op_config in ops:
|
||||||
|
op = op_config.get("op")
|
||||||
|
params = op_config.get("params", {})
|
||||||
|
if op == "normalize" and isinstance(data, np.ndarray):
|
||||||
|
# 简单归一化示例
|
||||||
|
if data.size == 0:
|
||||||
|
continue
|
||||||
|
min_val = params.get("min", 0)
|
||||||
|
max_val = params.get("max", 1)
|
||||||
|
data_min = float(data.min())
|
||||||
|
data_max = float(data.max())
|
||||||
|
denom = data_max - data_min
|
||||||
|
if denom == 0:
|
||||||
|
data = np.full_like(data, float(min_val), dtype=float)
|
||||||
|
else:
|
||||||
|
data = (data.astype(float) - data_min) / denom * (float(max_val) - float(min_val)) + float(min_val)
|
||||||
|
elif op == "scale" and isinstance(data, (int, float, np.ndarray)):
|
||||||
|
data = data * params.get("factor", 1.0)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def call_algo(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
try:
|
||||||
|
water_data.add_log(self.name, "Start Execution")
|
||||||
|
|
||||||
|
# 1. 提取并映射输入,同时应用预处理
|
||||||
|
algo_inputs = {}
|
||||||
|
for param, attr in self.input_mapping.items():
|
||||||
|
if hasattr(water_data, attr):
|
||||||
|
val = getattr(water_data, attr)
|
||||||
|
# 查找是否有针对该字段的预处理
|
||||||
|
relevant_ops = [op for op in self.preprocessing if op.get("field") == attr]
|
||||||
|
if relevant_ops:
|
||||||
|
val = self._apply_preprocessing(val, relevant_ops)
|
||||||
|
algo_inputs[param] = val
|
||||||
|
else:
|
||||||
|
raise AttributeError(f"Field {attr} not found in WaterSoundData")
|
||||||
|
|
||||||
|
# 2. 调用算法
|
||||||
|
results = self.algo_func(**algo_inputs)
|
||||||
|
|
||||||
|
# 3. 映射结果回 standardization 数据
|
||||||
|
if not isinstance(results, (list, tuple)):
|
||||||
|
results = [results]
|
||||||
|
|
||||||
|
for key, attr in self.output_mapping.items():
|
||||||
|
# key can be an index (int) or a key if results is dict
|
||||||
|
if isinstance(results, dict):
|
||||||
|
val = results[key]
|
||||||
|
else:
|
||||||
|
idx = int(key) if isinstance(key, str) and key.isdigit() else key
|
||||||
|
val = results[idx]
|
||||||
|
setattr(water_data, attr, val)
|
||||||
|
|
||||||
|
water_data.add_log(self.name, "Finished Execution")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
water_data.set_error(f"Algorithm {self.name} failed: {str(e)}")
|
||||||
|
|
||||||
|
return water_data
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
class CppBridge(AlgorithmBridge):
|
||||||
|
"""C++ 桥接插件 (ctypes 实现)"""
|
||||||
|
def __init__(self, name: str, lib_path: str, function_name: str,
|
||||||
|
input_mapping: Dict[str, str],
|
||||||
|
output_mapping: Dict[str, str],
|
||||||
|
preprocessing: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
requirements: Optional[Dict[str, Any]] = None,
|
||||||
|
output_specs: Optional[Dict[str, Any]] = None,
|
||||||
|
is_core: bool = False):
|
||||||
|
super().__init__(name, is_core)
|
||||||
|
self.lib_path = lib_path
|
||||||
|
self.function_name = function_name
|
||||||
|
self.input_mapping = input_mapping
|
||||||
|
self.output_mapping = output_mapping
|
||||||
|
self.preprocessing = preprocessing or []
|
||||||
|
self.requirements = requirements or {}
|
||||||
|
self.output_specs = output_specs or {}
|
||||||
|
self.algo_type = "cpp"
|
||||||
|
self._lib = None
|
||||||
|
self._func = None
|
||||||
|
|
||||||
|
def _load_lib(self):
|
||||||
|
"""延迟加载动态库"""
|
||||||
|
if self._lib is None:
|
||||||
|
if not os.path.exists(self.lib_path):
|
||||||
|
raise FileNotFoundError(f"C++ library not found: {self.lib_path}")
|
||||||
|
self._lib = ctypes.CDLL(self.lib_path)
|
||||||
|
self._func = getattr(self._lib, self.function_name)
|
||||||
|
# 注意:在 C 侧我们需要定义统一签名,例如:
|
||||||
|
# void func(double* in, int in_len, double* out, int out_len)
|
||||||
|
|
||||||
|
def call_algo(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
try:
|
||||||
|
water_data.add_log(self.name, "Start C++ Execution")
|
||||||
|
self._load_lib()
|
||||||
|
|
||||||
|
# 1. 准备输入
|
||||||
|
# 为了简化 Phase 3.3,我们假设目前只支持 1进1出 (double array)
|
||||||
|
# 实际生产中会根据 input_mapping 构造更复杂的结构
|
||||||
|
in_attr = list(self.input_mapping.values())[0]
|
||||||
|
in_data = getattr(water_data, in_attr)
|
||||||
|
|
||||||
|
if not isinstance(in_data, np.ndarray):
|
||||||
|
raise TypeError(f"C++ bridge expected numpy array for {in_attr}")
|
||||||
|
|
||||||
|
# 确保数据连续且为 float64 (double)
|
||||||
|
in_data = np.ascontiguousarray(in_data, dtype=np.float64)
|
||||||
|
in_ptr = in_data.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||||
|
in_len = ctypes.c_int(in_data.size)
|
||||||
|
|
||||||
|
# 2. 准备输出空间
|
||||||
|
# 同样简化为与输入等长
|
||||||
|
out_data = np.zeros_like(in_data)
|
||||||
|
out_ptr = out_data.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
|
||||||
|
out_len = ctypes.c_int(out_data.size)
|
||||||
|
|
||||||
|
# 3. 调用 C++ 函数
|
||||||
|
# 默认签名: void (double*, int, double*, int)
|
||||||
|
self._func(in_ptr, in_len, out_ptr, out_len)
|
||||||
|
|
||||||
|
# 4. 映射结果回 standardization 数据
|
||||||
|
out_attr = list(self.output_mapping.values())[0]
|
||||||
|
setattr(water_data, out_attr, out_data)
|
||||||
|
|
||||||
|
water_data.add_log(self.name, "Finished C++ Execution")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
water_data.set_error(f"C++ Algorithm {self.name} failed: {str(e)}")
|
||||||
|
|
||||||
|
return water_data
|
||||||
|
|
||||||
|
|
||||||
|
class MatlabBridge(AlgorithmBridge):
|
||||||
|
"""MATLAB/Octave 桥接插件 (支持 Engine/Oct2py/File 降级)"""
|
||||||
|
def __init__(self, name: str, m_path: str, function_name: str,
|
||||||
|
input_mapping: Dict[str, str],
|
||||||
|
output_mapping: Dict[str, str],
|
||||||
|
preprocessing: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
requirements: Optional[Dict[str, Any]] = None,
|
||||||
|
output_specs: Optional[Dict[str, Any]] = None,
|
||||||
|
is_core: bool = False):
|
||||||
|
super().__init__(name, is_core)
|
||||||
|
self.m_path = m_path # .m 文件所在目录
|
||||||
|
self.function_name = function_name
|
||||||
|
self.input_mapping = input_mapping
|
||||||
|
self.output_mapping = output_mapping
|
||||||
|
self.preprocessing = preprocessing or []
|
||||||
|
self.requirements = requirements or {}
|
||||||
|
self.output_specs = output_specs or {}
|
||||||
|
self.algo_type = "matlab"
|
||||||
|
self._engine = None
|
||||||
|
|
||||||
|
def _call_via_engine(self, inputs: Dict[str, Any]) -> Any:
|
||||||
|
"""方式 1: 使用官方 matlab.engine"""
|
||||||
|
import matlab.engine
|
||||||
|
if self._engine is None:
|
||||||
|
self._engine = matlab.engine.start_matlab()
|
||||||
|
self._engine.addpath(self.m_path)
|
||||||
|
|
||||||
|
func = getattr(self._engine, self.function_name)
|
||||||
|
# 将 numpy 数组转为 matlab 类型 (这里简化处理)
|
||||||
|
m_inputs = [matlab.double(v.tolist()) if isinstance(v, np.ndarray) else v for v in inputs.values()]
|
||||||
|
return func(*m_inputs, nargout=len(self.output_mapping))
|
||||||
|
|
||||||
|
def _call_via_oct2py(self, inputs: Dict[str, Any]) -> Any:
|
||||||
|
"""方式 2: 使用开源 oct2py (Octave)"""
|
||||||
|
from oct2py import octave
|
||||||
|
octave.addpath(self.m_path)
|
||||||
|
func = getattr(octave, self.function_name)
|
||||||
|
return func(*inputs.values())
|
||||||
|
|
||||||
|
def _call_via_file(self, inputs: Dict[str, Any]) -> Any:
|
||||||
|
"""方式 3: 文件兜底 (使用 .mat 文件交换)"""
|
||||||
|
from scipy.io import savemat, loadmat
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix="matlab_bridge_") as tmp_dir:
|
||||||
|
in_file = os.path.join(tmp_dir, "tmp_in.mat")
|
||||||
|
out_file = os.path.join(tmp_dir, "tmp_out.mat")
|
||||||
|
savemat(in_file, inputs)
|
||||||
|
|
||||||
|
# 构造执行脚本:加入 m_path,加载输入,调用函数,保存输出
|
||||||
|
m_path = os.path.abspath(self.m_path).replace("'", "''")
|
||||||
|
in_file_escaped = in_file.replace("'", "''")
|
||||||
|
out_file_escaped = out_file.replace("'", "''")
|
||||||
|
arglist = ",".join(inputs.keys())
|
||||||
|
script = (
|
||||||
|
f"addpath('{m_path}'); "
|
||||||
|
f"load('{in_file_escaped}'); "
|
||||||
|
f"results = {self.function_name}({arglist}); "
|
||||||
|
f"save('-mat', '{out_file_escaped}', 'results'); "
|
||||||
|
"exit;"
|
||||||
|
)
|
||||||
|
|
||||||
|
octave = shutil.which("octave") or shutil.which("octave-cli")
|
||||||
|
matlab = shutil.which("matlab")
|
||||||
|
if octave:
|
||||||
|
cmd = [octave, "--quiet", "--eval", script]
|
||||||
|
elif matlab:
|
||||||
|
cmd = [matlab, "-batch", script]
|
||||||
|
else:
|
||||||
|
raise RuntimeError("No MATLAB/Octave CLI found for file-based execution.")
|
||||||
|
|
||||||
|
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||||
|
|
||||||
|
res = loadmat(out_file)
|
||||||
|
return res.get("results")
|
||||||
|
|
||||||
|
def call_algo(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
try:
|
||||||
|
water_data.add_log(self.name, "Start MATLAB Execution")
|
||||||
|
|
||||||
|
# 准备输入
|
||||||
|
algo_inputs = {}
|
||||||
|
for param, attr in self.input_mapping.items():
|
||||||
|
algo_inputs[param] = getattr(water_data, attr)
|
||||||
|
|
||||||
|
# 自动寻优/降级逻辑
|
||||||
|
from src.env_detect import collect_env_info
|
||||||
|
env = collect_env_info()
|
||||||
|
|
||||||
|
if env["matlab"]["engine_available"]:
|
||||||
|
results = self._call_via_engine(algo_inputs)
|
||||||
|
elif env["matlab"]["oct2py_available"]:
|
||||||
|
results = self._call_via_oct2py(algo_inputs)
|
||||||
|
elif env["matlab"]["octave_cli"]:
|
||||||
|
results = self._call_via_file(algo_inputs)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("No MATLAB/Octave environment found for execution.")
|
||||||
|
|
||||||
|
# 结果回写 (支持单出/多出)
|
||||||
|
if not isinstance(results, (list, tuple)):
|
||||||
|
results = [results]
|
||||||
|
|
||||||
|
for key, attr in self.output_mapping.items():
|
||||||
|
idx = int(key) if key.isdigit() else 0
|
||||||
|
setattr(water_data, attr, results[idx])
|
||||||
|
|
||||||
|
water_data.add_log(self.name, "Finished MATLAB Execution")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
water_data.set_error(f"MATLAB Algorithm {self.name} failed: {str(e)}")
|
||||||
|
|
||||||
|
return water_data
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
def _has_module(module_name: str) -> bool:
|
||||||
|
"""安全检测 Python 模块是否可用"""
|
||||||
|
try:
|
||||||
|
return importlib.util.find_spec(module_name) is not None
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_lib_suffix() -> str:
|
||||||
|
"""获取当前平台的动态库后缀"""
|
||||||
|
system = platform.system().lower()
|
||||||
|
if system == "windows":
|
||||||
|
return ".dll"
|
||||||
|
elif system == "darwin":
|
||||||
|
return ".dylib"
|
||||||
|
return ".so"
|
||||||
|
|
||||||
|
|
||||||
|
def _check_ld_paths() -> List[str]:
|
||||||
|
"""检测动态库搜索路径"""
|
||||||
|
paths = []
|
||||||
|
for var in ["LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "PATH"]:
|
||||||
|
val = os.environ.get(var)
|
||||||
|
if val:
|
||||||
|
paths.extend([p for p in val.split(os.pathsep) if p])
|
||||||
|
return paths[:10] # 限制输出长度
|
||||||
|
|
||||||
|
|
||||||
|
def collect_env_info() -> Dict[str, Any]:
|
||||||
|
"""收集完整的环境信息"""
|
||||||
|
compilers = {
|
||||||
|
"gcc": shutil.which("gcc"),
|
||||||
|
"g++": shutil.which("g++"),
|
||||||
|
"clang": shutil.which("clang"),
|
||||||
|
"clang++": shutil.which("clang++"),
|
||||||
|
"cl": shutil.which("cl"), # MSVC
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"python": {
|
||||||
|
"version": sys.version.split()[0],
|
||||||
|
"executable": sys.executable,
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"os": platform.system(),
|
||||||
|
"release": platform.release(),
|
||||||
|
"machine": platform.machine(),
|
||||||
|
"lib_suffix": _get_lib_suffix(),
|
||||||
|
},
|
||||||
|
"matlab": {
|
||||||
|
"engine_available": _has_module("matlab.engine") or _has_module("matlab"),
|
||||||
|
"oct2py_available": _has_module("oct2py"),
|
||||||
|
"octave_cli": shutil.which("octave") or shutil.which("octave-cli"),
|
||||||
|
},
|
||||||
|
"cpp": {
|
||||||
|
"compilers": {k: v for k, v in compilers.items() if v},
|
||||||
|
"any_available": any(compilers.values()),
|
||||||
|
},
|
||||||
|
"ld_paths": _check_ld_paths(),
|
||||||
|
"scipy_available": _has_module("scipy"),
|
||||||
|
"numpy_available": _has_module("numpy"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_algo_types() -> List[str]:
|
||||||
|
"""根据环境返回可用的算法类型列表"""
|
||||||
|
env = collect_env_info()
|
||||||
|
types = ["python"] # Python 始终可用
|
||||||
|
|
||||||
|
if env["matlab"]["engine_available"] or env["matlab"]["oct2py_available"] or env["matlab"]["octave_cli"]:
|
||||||
|
types.append("matlab")
|
||||||
|
|
||||||
|
if env["cpp"]["any_available"]:
|
||||||
|
types.append("cpp")
|
||||||
|
|
||||||
|
return types
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import numpy as np
|
||||||
|
from typing import Dict, Optional, List, Any
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WaterSoundData:
|
||||||
|
"""唯一数据交换标准 (WaterSoundData)"""
|
||||||
|
# 1. 基础原始数据
|
||||||
|
raw_data: Optional[np.ndarray] = None # 原始声呐数据
|
||||||
|
sample_rate: float = 0.0 # 采样率
|
||||||
|
collect_time: str = "" # 数据采集时间
|
||||||
|
sea_env: Dict[str, Any] = field(default_factory=dict) # 海洋环境参数(声速、温度、盐度等)
|
||||||
|
|
||||||
|
# 2. 处理过程数据
|
||||||
|
clean_data: Optional[np.ndarray] = None # 清洗后数据
|
||||||
|
denoised_data: Optional[np.ndarray] = None # 降噪后数据
|
||||||
|
stft_data: Optional[np.ndarray] = None # STFT时频数据
|
||||||
|
feat_data: Optional[np.ndarray] = None # 特征数组
|
||||||
|
|
||||||
|
# 3. 结果数据
|
||||||
|
final_result: Optional[np.ndarray] = None # 最终处理结果
|
||||||
|
core_index: Dict[str, Any] = field(default_factory=dict) # 核心指标(信噪比、数据完整性等)
|
||||||
|
vis_data: Dict[str, Any] = field(default_factory=dict) # 可视化数据
|
||||||
|
|
||||||
|
# 4. 元信息
|
||||||
|
status: str = "init" # 数据状态:init/processing/finished/error
|
||||||
|
error_info: str = "" # 错误信息
|
||||||
|
log: List[Dict[str, Any]] = field(default_factory=list) # 执行日志
|
||||||
|
algo_version: Dict[str, str] = field(default_factory=dict) # 算法版本记录
|
||||||
|
|
||||||
|
def add_log(self, step: str, action: str, info: str = ""):
|
||||||
|
"""记录执行日志"""
|
||||||
|
self.log.append({
|
||||||
|
"step": step,
|
||||||
|
"action": action,
|
||||||
|
"info": info,
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||||
|
})
|
||||||
|
|
||||||
|
def set_error(self, info: str):
|
||||||
|
"""设置错误信息"""
|
||||||
|
self.status = "error"
|
||||||
|
self.error_info = info
|
||||||
|
self.add_log("System", "Error", info)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import numpy as np
|
||||||
|
from scipy.signal import butter, filtfilt
|
||||||
|
|
||||||
|
def dc_removal_and_bandpass(raw_data: np.ndarray, fs: float, lowcut: float = 10.0, highcut: float = 400.0) -> np.ndarray:
|
||||||
|
"""去直流 + 带通滤波"""
|
||||||
|
raw_data = np.asarray(raw_data)
|
||||||
|
if raw_data.size == 0:
|
||||||
|
return raw_data
|
||||||
|
if fs <= 0:
|
||||||
|
raise ValueError(f"fs 必须为正数,当前 fs={fs}")
|
||||||
|
|
||||||
|
# 1. 去直流
|
||||||
|
data = raw_data - np.mean(raw_data)
|
||||||
|
# 2. 带通滤波
|
||||||
|
nyq = 0.5 * fs
|
||||||
|
if not (0 < lowcut < highcut < nyq):
|
||||||
|
raise ValueError(f"带通参数不合法:需要 0 < lowcut < highcut < fs/2;当前 lowcut={lowcut}, highcut={highcut}, fs={fs}")
|
||||||
|
low = lowcut / nyq
|
||||||
|
high = highcut / nyq
|
||||||
|
b, a = butter(4, [low, high], btype='band')
|
||||||
|
padlen = 3 * (max(len(a), len(b)) - 1)
|
||||||
|
if data.size <= padlen:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
return filtfilt(b, a, data)
|
||||||
|
except ValueError:
|
||||||
|
return data
|
||||||
|
|
||||||
|
def stft_magnitude(clean_data: np.ndarray, nperseg: int = 256) -> np.ndarray:
|
||||||
|
"""计算 STFT 幅度谱"""
|
||||||
|
clean_data = np.asarray(clean_data)
|
||||||
|
if clean_data.size == 0:
|
||||||
|
return np.empty((0, 0))
|
||||||
|
from scipy.signal import stft
|
||||||
|
_, _, zxx = stft(clean_data, nperseg=nperseg)
|
||||||
|
return np.abs(zxx)
|
||||||
|
|
||||||
|
def snr_estimation(raw_data: np.ndarray) -> float:
|
||||||
|
"""简易信噪比估计 (信号功率/噪声功率)"""
|
||||||
|
raw_data = np.asarray(raw_data)
|
||||||
|
if raw_data.size == 0:
|
||||||
|
raise ValueError("raw_data 为空,无法计算 SNR")
|
||||||
|
|
||||||
|
# 假设均值附近是噪声,这里极简逻辑:RMS / STD
|
||||||
|
# 生产中通常用静默段估计
|
||||||
|
signal_power = float(np.mean(raw_data.astype(float) ** 2))
|
||||||
|
noise_power = float(np.var(raw_data.astype(float))) # 简化逻辑
|
||||||
|
if noise_power == 0:
|
||||||
|
return 100.0
|
||||||
|
return float(10 * np.log10(signal_power / noise_power))
|
||||||
|
|
||||||
|
def quality_check(raw_data: np.ndarray) -> dict:
|
||||||
|
"""掉点/饱和检测"""
|
||||||
|
raw_data = np.asarray(raw_data)
|
||||||
|
if raw_data.size == 0:
|
||||||
|
return {"saturation": False, "dropouts": 0, "max_abs_val": 0.0, "quality_score": 0.0}
|
||||||
|
|
||||||
|
max_val = float(np.max(np.abs(raw_data)))
|
||||||
|
saturation = bool(max_val > 0.95) # 假设归一化到1
|
||||||
|
dropouts = int(np.sum(raw_data == 0))
|
||||||
|
score = 100.0 - (10.0 if saturation else 0.0) - (dropouts / raw_data.size * 100.0)
|
||||||
|
return {
|
||||||
|
"saturation": saturation,
|
||||||
|
"dropouts": dropouts,
|
||||||
|
"max_abs_val": max_val,
|
||||||
|
"quality_score": float(max(0.0, score)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def mock_clean(raw_data: np.ndarray) -> np.ndarray:
|
||||||
|
return raw_data * 1.1
|
||||||
|
|
||||||
|
def mock_feat(clean_data: np.ndarray) -> float:
|
||||||
|
return float(np.mean(clean_data))
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
// 统一签名:输入指针, 输入长度, 输出指针, 输出长度
|
||||||
|
void multiply_by_factor_cpp(double* in, int in_len, double* out, int out_len) {
|
||||||
|
int n = (in_len < out_len) ? in_len : out_len;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
out[i] = in[i] * 1.5; // 模拟 C++ 侧的高效计算
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
function out = bandpass_filter_m(in_data, fs)
|
||||||
|
% 简易 MATLAB 带通滤波模拟
|
||||||
|
% 实际生产中会使用 designfilt 或 butter
|
||||||
|
disp(['MATLAB Processing with fs: ', num2str(fs)]);
|
||||||
|
out = in_data * 0.9; % 模拟处理
|
||||||
|
end
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import numpy as np
|
||||||
|
from src.models import WaterSoundData
|
||||||
|
from src.bridges import AlgorithmBridge
|
||||||
|
|
||||||
|
class CoreScheduler:
|
||||||
|
"""核心调度层"""
|
||||||
|
def __init__(self):
|
||||||
|
self.flow: List[AlgorithmBridge] = []
|
||||||
|
|
||||||
|
def set_flow(self, algo_list: List[AlgorithmBridge]):
|
||||||
|
"""设置算法调度流程"""
|
||||||
|
self.flow = algo_list
|
||||||
|
|
||||||
|
def execute(self, water_data: WaterSoundData) -> WaterSoundData:
|
||||||
|
"""执行调度流程"""
|
||||||
|
water_data.status = "processing"
|
||||||
|
water_data.add_log("Scheduler", "Starting Pipeline")
|
||||||
|
|
||||||
|
for algo in self.flow:
|
||||||
|
if water_data.status == "error":
|
||||||
|
# Check for fault tolerance logic here later
|
||||||
|
# For now, stop on error if not handled
|
||||||
|
water_data.add_log("Scheduler", f"Execution halted due to error in previous step")
|
||||||
|
break
|
||||||
|
|
||||||
|
water_data = algo.call_algo(water_data)
|
||||||
|
|
||||||
|
if water_data.status != "error":
|
||||||
|
water_data.status = "finished"
|
||||||
|
water_data.add_log("Scheduler", "Pipeline Completed Successfully")
|
||||||
|
|
||||||
|
return water_data
|
||||||
|
|
||||||
|
def validate_flow(self, water_data: WaterSoundData) -> List[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
实时校验全流程
|
||||||
|
返回错误列表,每个错误包含 'message' 和 'suggestion'
|
||||||
|
"""
|
||||||
|
from src.validator import FlowValidator
|
||||||
|
from src.env_detect import collect_env_info
|
||||||
|
issues = []
|
||||||
|
env_info = collect_env_info()
|
||||||
|
|
||||||
|
def _dummy_value(expected_type: str):
|
||||||
|
type_map = {
|
||||||
|
"ndarray": np.zeros(1),
|
||||||
|
"float": 0.0,
|
||||||
|
"int": 0,
|
||||||
|
"dict": {},
|
||||||
|
}
|
||||||
|
return type_map.get(expected_type, np.zeros(1))
|
||||||
|
|
||||||
|
# 模拟演练 WaterSoundData 的状态变化
|
||||||
|
temp_data = WaterSoundData()
|
||||||
|
# 复制初始数据
|
||||||
|
for attr in ['raw_data', 'sample_rate', 'collect_time', 'sea_env']:
|
||||||
|
setattr(temp_data, attr, getattr(water_data, attr))
|
||||||
|
|
||||||
|
for bridge in self.flow:
|
||||||
|
requirements = getattr(bridge, "requirements", None)
|
||||||
|
algo_type = getattr(bridge, "algo_type", "python")
|
||||||
|
|
||||||
|
# 校验输入映射(优先走聚合校验:字段存在性/类型/维度/完整性/兼容性)
|
||||||
|
if requirements and hasattr(bridge, 'input_mapping'):
|
||||||
|
step_errors = FlowValidator.validate_step(
|
||||||
|
temp_data,
|
||||||
|
requirements=requirements,
|
||||||
|
input_mapping=getattr(bridge, "input_mapping", None),
|
||||||
|
algo_type=algo_type,
|
||||||
|
env_info=env_info,
|
||||||
|
)
|
||||||
|
for err in step_errors:
|
||||||
|
issues.append({"algo": bridge.name, "message": err["message"], "suggestion": err["suggestion"]})
|
||||||
|
elif hasattr(bridge, 'input_mapping'):
|
||||||
|
# 向后兼容:无元数据时仅做字段存在性校验
|
||||||
|
for _, attr in bridge.input_mapping.items():
|
||||||
|
exists, msg, sug = FlowValidator.check_field_existence(temp_data, attr)
|
||||||
|
if not exists:
|
||||||
|
issues.append({"algo": bridge.name, "message": msg, "suggestion": sug})
|
||||||
|
|
||||||
|
# 模拟执行(只改状态,不跑真实算法,除非是轻量级的)
|
||||||
|
if hasattr(bridge, 'output_mapping'):
|
||||||
|
output_specs = getattr(bridge, "output_specs", None) or {}
|
||||||
|
for key, attr in bridge.output_mapping.items():
|
||||||
|
# 按声明的输出类型填充哑元数据,让后续步骤能做类型/维度校验
|
||||||
|
spec = output_specs.get(attr) or {}
|
||||||
|
expected_type = spec.get("type") or "ndarray"
|
||||||
|
setattr(temp_data, attr, _dummy_value(expected_type))
|
||||||
|
|
||||||
|
return issues
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
class TemplateManager:
|
||||||
|
"""接口配置模板管理类 (Interface Template Management)"""
|
||||||
|
def __init__(self, templates_dir: str):
|
||||||
|
self.templates_dir = templates_dir
|
||||||
|
os.makedirs(self.templates_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def save_template(self, name: str, mapping_data: Dict[str, Any]):
|
||||||
|
"""保存接口配置模板"""
|
||||||
|
file_path = os.path.join(self.templates_dir, f"{name}.json")
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(mapping_data, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
def load_template(self, name: str) -> Dict[str, Any]:
|
||||||
|
"""加载接口配置模板"""
|
||||||
|
file_path = os.path.join(self.templates_dir, f"{name}.json")
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise FileNotFoundError(f"Template {name} not found")
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def list_templates(self) -> List[str]:
|
||||||
|
"""列出所有可用模板"""
|
||||||
|
return [f.replace('.json', '') for f in os.listdir(self.templates_dir) if f.endswith('.json')]
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
import numpy as np
|
||||||
|
from src.models import WaterSoundData
|
||||||
|
|
||||||
|
class FlowValidator:
|
||||||
|
"""五类校验模块 (5-type Validation Module)"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_step(
|
||||||
|
data: WaterSoundData,
|
||||||
|
requirements: Dict[str, Any],
|
||||||
|
input_mapping: Optional[Dict[str, str]] = None,
|
||||||
|
algo_type: str = "python",
|
||||||
|
env_info: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> List[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
进行 5 类校验,并输出人话+修复建议
|
||||||
|
requirements: 算法元数据中的 inputs 定义
|
||||||
|
"""
|
||||||
|
errors: List[Dict[str, str]] = []
|
||||||
|
env_info = env_info or {}
|
||||||
|
input_mapping = input_mapping or {}
|
||||||
|
|
||||||
|
# 4) 参数完整性(这里用于校验“输入映射是否齐全”)
|
||||||
|
required_params = list(requirements.keys())
|
||||||
|
ok, msg, sug = FlowValidator.check_completeness(input_mapping, required_params)
|
||||||
|
if not ok:
|
||||||
|
errors.append({"message": msg, "suggestion": sug})
|
||||||
|
|
||||||
|
for param, spec in requirements.items():
|
||||||
|
field_name = input_mapping.get(param) or param
|
||||||
|
|
||||||
|
# 1) 字段存在性
|
||||||
|
ok, msg, sug = FlowValidator.check_field_existence(data, field_name)
|
||||||
|
if not ok:
|
||||||
|
errors.append({"message": msg, "suggestion": sug})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 2) 类型校验
|
||||||
|
expected_type = (spec or {}).get("type")
|
||||||
|
if expected_type:
|
||||||
|
ok, msg, sug = FlowValidator.check_data_type(data, field_name, expected_type)
|
||||||
|
if not ok:
|
||||||
|
errors.append({"message": msg, "suggestion": sug})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 3) 维度校验
|
||||||
|
expected_dim = (spec or {}).get("dim")
|
||||||
|
if expected_dim is not None:
|
||||||
|
ok, msg, sug = FlowValidator.check_dimensions(data, field_name, expected_dim)
|
||||||
|
if not ok:
|
||||||
|
errors.append({"message": msg, "suggestion": sug})
|
||||||
|
|
||||||
|
# 5) 环境/跨语言兼容性(Phase 2:Python 放行,其他给出建议)
|
||||||
|
ok, msg, sug = FlowValidator.check_compatibility(algo_type, env_info)
|
||||||
|
if not ok:
|
||||||
|
errors.append({"message": msg, "suggestion": sug})
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_field_existence(data: WaterSoundData, field_name: str) -> Tuple[bool, str, str]:
|
||||||
|
"""1. 字段存在性校验"""
|
||||||
|
if not hasattr(data, field_name) or getattr(data, field_name) is None:
|
||||||
|
return False, f"缺少必要字段: {field_name}", f"建议:请在流程前置步骤中增加输出 '{field_name}' 的算法,或手动配置输入。"
|
||||||
|
return True, "", ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_data_type(data: WaterSoundData, field_name: str, expected_type: str) -> Tuple[bool, str, str]:
|
||||||
|
"""2. 类型校验"""
|
||||||
|
val = getattr(data, field_name)
|
||||||
|
actual_type = type(val).__name__
|
||||||
|
|
||||||
|
type_map = {
|
||||||
|
"ndarray": np.ndarray,
|
||||||
|
"float": (float, np.float32, np.float64),
|
||||||
|
"int": int,
|
||||||
|
"dict": dict
|
||||||
|
}
|
||||||
|
|
||||||
|
target = type_map.get(expected_type)
|
||||||
|
if target and not isinstance(val, target):
|
||||||
|
return False, f"字段 '{field_name}' 类型不匹配", f"期望 {expected_type},实际为 {actual_type}。建议:检查前置算法的输出类型配置。"
|
||||||
|
return True, "", ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_dimensions(data: WaterSoundData, field_name: str, expected_dim: List[Optional[int]]) -> Tuple[bool, str, str]:
|
||||||
|
"""3. 维度校验"""
|
||||||
|
val = getattr(data, field_name)
|
||||||
|
if not isinstance(val, np.ndarray):
|
||||||
|
return True, "", "" # 非数组不校验维度
|
||||||
|
|
||||||
|
actual_shape = val.shape
|
||||||
|
if len(actual_shape) != len(expected_dim):
|
||||||
|
return False, f"字段 '{field_name}' 维度不匹配", f"期望 {len(expected_dim)} 维,实际 {len(actual_shape)} 维。建议:检查数据切片或重采样参数。"
|
||||||
|
|
||||||
|
for i, d in enumerate(expected_dim):
|
||||||
|
if d is not None and actual_shape[i] != d:
|
||||||
|
return False, f"字段 '{field_name}' 第 {i} 维长度不符", f"期望 {d},实际 {actual_shape[i]}。建议:调整前置算子的输出规格。"
|
||||||
|
|
||||||
|
return True, "", ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_completeness(params: Dict[str, Any], required_params: List[str]) -> Tuple[bool, str, str]:
|
||||||
|
"""4. 参数完整性校验"""
|
||||||
|
missing = [p for p in required_params if p not in params or params[p] is None]
|
||||||
|
if missing:
|
||||||
|
return False, f"缺少必填参数/映射: {', '.join(missing)}", "建议:在接口配置/算法配置面板中补齐必填项。"
|
||||||
|
return True, "", ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_compatibility(algo_type: str, env_info: Dict[str, Any]) -> Tuple[bool, str, str]:
|
||||||
|
"""5. 跨语言兼容性校验(结合环境自检结果)"""
|
||||||
|
if algo_type == "python":
|
||||||
|
return True, "", ""
|
||||||
|
|
||||||
|
if algo_type == "cpp":
|
||||||
|
if env_info.get("cpp", {}).get("any_available"):
|
||||||
|
return True, "", ""
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
"当前环境未检测到可用的 C/C++ 工具链",
|
||||||
|
"建议:安装 MinGW/GCC/MSVC(或在交付包中提供已编译的动态库并更新检测策略)。",
|
||||||
|
)
|
||||||
|
|
||||||
|
if algo_type == "matlab":
|
||||||
|
matlab_info = env_info.get("matlab", {})
|
||||||
|
if matlab_info.get("engine_available") or matlab_info.get("oct2py_available") or matlab_info.get("octave_cli"):
|
||||||
|
return True, "", ""
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
"当前环境未检测到 MATLAB/Octave",
|
||||||
|
"建议:安装 MATLAB Engine 或 Octave+oct2py,或配置 Octave CLI 以启用文件兜底模式。",
|
||||||
|
)
|
||||||
|
|
||||||
|
return False, f"暂不支持 {algo_type} 算法类型", "建议:请确认算法元数据中的 type 配置,或在 Phase 3 扩展桥接插件。"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# tests package init
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from src.models import WaterSoundData
|
||||||
|
from src.bridges import PythonBridge
|
||||||
|
from src.scheduler import CoreScheduler
|
||||||
|
|
||||||
|
def test_integration():
|
||||||
|
print("Starting integration test...")
|
||||||
|
|
||||||
|
# 1. Prepare Data
|
||||||
|
raw = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
|
||||||
|
wsd = WaterSoundData(raw_data=raw, sample_rate=100.0)
|
||||||
|
|
||||||
|
# 2. Define Algos
|
||||||
|
def mock_clean(raw_data):
|
||||||
|
return raw_data * 2.0
|
||||||
|
|
||||||
|
def mock_feat(clean_data):
|
||||||
|
return np.mean(clean_data)
|
||||||
|
|
||||||
|
# 3. Create Bridges
|
||||||
|
clean_bridge = PythonBridge("Clean", mock_clean,
|
||||||
|
{"raw_data": "raw_data"}, {"0": "clean_data"})
|
||||||
|
feat_bridge = PythonBridge("Feature", mock_feat,
|
||||||
|
{"clean_data": "clean_data"}, {"0": "feat_data"})
|
||||||
|
|
||||||
|
# 4. Schedule
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([clean_bridge, feat_bridge])
|
||||||
|
|
||||||
|
# 5. Execute
|
||||||
|
final_wsd = scheduler.execute(wsd)
|
||||||
|
|
||||||
|
# 6. Verify
|
||||||
|
print(f"Status: {final_wsd.status}")
|
||||||
|
print(f"Log length: {len(final_wsd.log)}")
|
||||||
|
print(f"Clean data: {final_wsd.clean_data}")
|
||||||
|
print(f"Feat data: {final_wsd.feat_data}")
|
||||||
|
|
||||||
|
assert final_wsd.status == "finished"
|
||||||
|
assert np.array_equal(final_wsd.clean_data, raw * 2.0)
|
||||||
|
assert final_wsd.feat_data == np.mean(raw * 2.0)
|
||||||
|
|
||||||
|
print("Integration test passed!")
|
||||||
|
|
||||||
|
def test_validation():
|
||||||
|
print("Starting validation test...")
|
||||||
|
from src.validator import FlowValidator
|
||||||
|
|
||||||
|
# 1. Test Field Existence
|
||||||
|
wsd = WaterSoundData()
|
||||||
|
exists, msg, sug = FlowValidator.check_field_existence(wsd, "raw_data")
|
||||||
|
assert not exists
|
||||||
|
assert "缺少必要字段" in msg
|
||||||
|
print("Field existence check passed.")
|
||||||
|
|
||||||
|
# 2. Test Data Type
|
||||||
|
wsd.raw_data = "not a numpy array"
|
||||||
|
valid, msg, sug = FlowValidator.check_data_type(wsd, "raw_data", "ndarray")
|
||||||
|
assert not valid
|
||||||
|
assert "类型不匹配" in msg
|
||||||
|
print("Data type check passed.")
|
||||||
|
|
||||||
|
# 3. Test Dimensions
|
||||||
|
wsd.raw_data = np.zeros((10, 10))
|
||||||
|
valid, msg, sug = FlowValidator.check_dimensions(wsd, "raw_data", [10]) # Expected 1D, got 2D
|
||||||
|
assert not valid
|
||||||
|
assert "维度不匹配" in msg
|
||||||
|
print("Dimension check passed.")
|
||||||
|
|
||||||
|
# 4. Test Scheduler validation
|
||||||
|
def mock_clean(raw_data): return raw_data
|
||||||
|
bridge = PythonBridge("Clean", mock_clean, {"raw_data": "raw_data"}, {"0": "clean_data"})
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([bridge])
|
||||||
|
|
||||||
|
empty_wsd = WaterSoundData()
|
||||||
|
issues = scheduler.validate_flow(empty_wsd)
|
||||||
|
assert len(issues) == 1
|
||||||
|
assert issues[0]['algo'] == "Clean"
|
||||||
|
print("Scheduler validation flow passed.")
|
||||||
|
|
||||||
|
def test_sea_trial_pipeline():
|
||||||
|
print("Starting sea-trial pipeline test...")
|
||||||
|
from src.sample_algorithms import dc_removal_and_bandpass, quality_check, snr_estimation
|
||||||
|
|
||||||
|
# Use a simple synthetic signal (length > filtfilt padlen)
|
||||||
|
t = np.linspace(0, 1.0, 2000, endpoint=False)
|
||||||
|
raw = (0.5 * np.sin(2 * np.pi * 50 * t) + 0.05 * np.random.randn(t.size)).astype(float)
|
||||||
|
wsd = WaterSoundData(raw_data=raw, sample_rate=1000.0)
|
||||||
|
|
||||||
|
b1 = PythonBridge("去直流+带通", dc_removal_and_bandpass,
|
||||||
|
{"raw_data": "raw_data", "fs": "sample_rate"}, {"0": "clean_data"})
|
||||||
|
b2 = PythonBridge("质量巡检", quality_check,
|
||||||
|
{"raw_data": "raw_data"}, {"0": "core_index"})
|
||||||
|
b3 = PythonBridge("SNR估计", snr_estimation,
|
||||||
|
{"raw_data": "clean_data"}, {"0": "snr_value"})
|
||||||
|
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([b1, b2, b3])
|
||||||
|
result = scheduler.execute(wsd)
|
||||||
|
|
||||||
|
assert result.status == "finished"
|
||||||
|
assert isinstance(result.core_index, dict)
|
||||||
|
assert "quality_score" in result.core_index
|
||||||
|
assert hasattr(result, "snr_value")
|
||||||
|
print("Sea-trial pipeline test passed.")
|
||||||
|
|
||||||
|
def test_cpp_bridge():
|
||||||
|
print("Starting C++ bridge test...")
|
||||||
|
from src.bridges import CppBridge
|
||||||
|
|
||||||
|
lib_path = "src/sample_algos_cpp/simple_ops.so"
|
||||||
|
if not os.path.exists(lib_path):
|
||||||
|
print("C++ library not found, skipping CPP test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
raw = np.array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=np.float64)
|
||||||
|
wsd = WaterSoundData(clean_data=raw)
|
||||||
|
|
||||||
|
cpp_bridge = CppBridge(
|
||||||
|
"GainCPP",
|
||||||
|
lib_path,
|
||||||
|
"multiply_by_factor_cpp",
|
||||||
|
{"in_data": "clean_data"},
|
||||||
|
{"0": "final_result"}
|
||||||
|
)
|
||||||
|
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([cpp_bridge])
|
||||||
|
result = scheduler.execute(wsd)
|
||||||
|
|
||||||
|
if result.status != "finished":
|
||||||
|
print(f"C++ Bridge Execution Failed: {result.error_info}")
|
||||||
|
for l in result.log:
|
||||||
|
print(f" Log: {l}")
|
||||||
|
|
||||||
|
assert result.status == "finished"
|
||||||
|
assert np.allclose(result.final_result, raw * 1.5)
|
||||||
|
print("C++ bridge test passed.")
|
||||||
|
|
||||||
|
def test_matlab_bridge_fallback():
|
||||||
|
print("Starting MATLAB bridge fallback test...")
|
||||||
|
from src.bridges import MatlabBridge
|
||||||
|
|
||||||
|
# 我们知道当前环境没有 MATLAB/Octave,所以预期它会抛出 RuntimeError
|
||||||
|
m_bridge = MatlabBridge(
|
||||||
|
"FilterM",
|
||||||
|
"src/sample_algos_matlab",
|
||||||
|
"bandpass_filter_m",
|
||||||
|
{"in_data": "raw_data", "fs": "sample_rate"},
|
||||||
|
{"0": "clean_data"}
|
||||||
|
)
|
||||||
|
|
||||||
|
wsd = WaterSoundData(raw_data=np.array([1, 2, 3]), sample_rate=1000.0)
|
||||||
|
scheduler = CoreScheduler()
|
||||||
|
scheduler.set_flow([m_bridge])
|
||||||
|
result = scheduler.execute(wsd)
|
||||||
|
|
||||||
|
# 预期失败并记录错误
|
||||||
|
assert result.status == "error"
|
||||||
|
assert "No MATLAB/Octave environment found" in result.error_info
|
||||||
|
print("MATLAB bridge fallback test passed (caught expected error).")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_integration()
|
||||||
|
test_validation()
|
||||||
|
test_sea_trial_pipeline()
|
||||||
|
test_cpp_bridge()
|
||||||
|
test_matlab_bridge_fallback()
|
||||||
在新工单中引用
屏蔽一个用户