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(""" """, unsafe_allow_html=True) def main(): set_design_system() st.sidebar.markdown("

🌊 HYD-SCHED

", unsafe_allow_html=True) st.sidebar.markdown("

海音达算法智能调度平台 v3.5

", 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("
", unsafe_allow_html=True) st.sidebar.markdown("""

平台状态: 🟢 已就绪

环境检测: ✅ 2.0.4

""", unsafe_allow_html=True) def show_home(): st.markdown("""

🌊 海音达智能调度处理系统

全协议集成 · 五维实时校验 · 跨语言桥接驱动


✅ Python Native ⚡ C++ Performance 📊 MATLAB Legacy
""", unsafe_allow_html=True) # 环境诊断与建议 (Phase 5) from src.env_detect import get_actionable_suggestions suggestions = get_actionable_suggestions() if suggestions: st.error("⚠️ 检测到环境瓶颈") for sug in suggestions: with st.expander(f"🛠️ {sug['target']} - {sug['reason']}"): st.code(sug['fix'], language="bash") else: st.success("✨ 系统环境全量兼容 (v2.0.4)") 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, col4 = st.columns(4) col1.metric("Python", "✅ 可用") col2.metric("MATLAB/Octave", "✅ 可用" if "matlab" in available_types else "❌ 不可用") col3.metric("C++ 运行时", "✅ 可用" if "cpp" in available_types else "❌ 不可用") col4.metric("Web 桥接", "✅ 可用" if "web" 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'
{algo_type.upper()}
', 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')}`") elif algo_type == "web": endpoint = algo.get('endpoint', 'N/A') st.write(f"**端点**: `{endpoint}`") if st.button(f"🌐 测试连接: {algo['name']}", key=f"ping_{algo['name']}"): from src.env_detect import check_endpoint_active active, msg = check_endpoint_active(endpoint) if active: st.success(f"✅ 联通正常: {msg}") else: st.error(f"❌ 无法访问: {msg}") 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 = '
' for i, algo in enumerate(st.session_state.pipeline): algo_type = algo.get("type", "python") badge_class = f"badge-{algo_type}" flow_html += f'''
STEP {i+1}
{algo["name"]}
{algo_type.upper()}
''' if i < len(st.session_state.pipeline) - 1: flow_html += '
➡
' flow_html += '
' 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) # 检测是否有 web 类型算法需要建模参数 has_web_algo = any(a.get("type") == "web" for a in st.session_state.pipeline) # 声学建模参数面板(仅当链路中包含 web 算法时展开) with st.expander("🌊 声学建模参数 (Web 算法)", expanded=has_web_algo): st.caption("以下参数用于 Bellhop/Kraken/RAM 等远程声学建模服务") col_freq, col_time = st.columns(2) with col_freq: modeling_freq = st.number_input("建模频率 (Hz)", min_value=1.0, value=100.0, step=10.0) with col_time: time_types = ["Annual", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "Winter", "Spring", "Summer", "Autumn"] modeling_time_type = st.selectbox("时间段", time_types) col_src, col_rcv = st.columns(2) with col_src: st.markdown("**声源位置**") source_lat = st.number_input("纬度 (°)", value=19.0, step=0.1, key="src_lat") source_lon = st.number_input("经度 (°)", value=115.0, step=0.1, key="src_lon") source_depth = st.number_input("深度 (m)", min_value=0, value=50, step=10, key="src_depth") with col_rcv: st.markdown("**接收位置**") receive_lat = st.number_input("纬度 (°)", value=20.0, step=0.1, key="rcv_lat") receive_lon = st.number_input("经度 (°)", value=116.0, step=0.1, key="rcv_lon") receive_depth = st.number_input("深度 (m)", min_value=0, value=3000, step=100, key="rcv_depth") 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), sample_rate_unit="Hz", channel_layout="time_first", coordinate_system="WGS84", collect_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), sea_env={ "modeling": { "freq_hz": float(modeling_freq), "source_depth_m": int(source_depth), "receive_depth_m": int(receive_depth), "source_lat": float(source_lat), "source_lon": float(source_lon), "receive_lat": float(receive_lat), "receive_lon": float(receive_lon), "time_type": modeling_time_type, } } ) # 自动映射 (Phase 2 简化版:直接使用元数据中的映射) scheduler = CoreScheduler() bridges = [] for algo in st.session_state.pipeline: algo_type = algo.get("type", "python") 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() } if algo_type == "web": # Web 类型使用 Key 映射 (如 transmission_loss -> vis_data.tl_result_path) out_map = { field_name: (spec or {}).get("field") or field_name for field_name, spec in (algo.get("outputs") or {}).items() } else: # 传统类型使用 Index 映射 (如 0 -> clean_data) 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 if algo_type == "python": try: algo_func = load_algo_func( algo.get("module", ""), algo.get("function", ""), expected_sha256=algo.get("sha256") or None, ) except Exception as e: st.error(f"算法加载失败:{algo.get('name', '')} - {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 {}, # 包含 argtypes/restype 如果在 JSON 中定义 output_specs=algo.get("outputs") or {}, expected_sha256=algo.get("sha256") or None, isolate=bool(algo.get("isolate", False)), ) 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 {}, expected_sha256=algo.get("sha256") or None, ) elif algo_type == "web": from src.bridges import WebBridge bridge = WebBridge( algo["name"], algo.get("endpoint", ""), in_map, out_map, method=algo.get("method", "POST"), timeout_sec=algo.get("timeout_sec", 300), slot_key=algo.get("slot_key"), fixed_params=algo.get("fixed_params"), sanitize_output=algo.get("sanitize_output", True), ) 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), sample_rate_unit="Hz", channel_layout="time_first", coordinate_system="WGS84", collect_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S") ) # 预设模板执行 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("""

📊 巡检简报

""", 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("""

🚑 异常中心 (Exception Hub)

聚合全链路校验与执行错误,支持一键导出排障包。

""", 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"""
❌ {issue.get('algo', 'Global')}
{issue.get('message', '')}
💡 修复建议: {issue.get('suggestion', '')}
""", 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'状态: {status.upper()}', 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()