| | |
| | | from datetime import datetime, timedelta |
| | | from app.services.extruder_service import ExtruderService |
| | | from app.services.main_process_service import MainProcessService |
| | | from app.services.data_processing_service import DataProcessingService |
| | | |
| | | # 稳态识别类定义 |
| | | class SteadyStateDetector: |
| | | def __init__(self): |
| | | self.data_processor = DataProcessingService() |
| | | |
| | | def preprocess_data(self, df, weight_col='metered_weight', window_size=20): |
| | | if df is None or df.empty: |
| | | return df |
| | | |
| | | df_processed = df.copy() |
| | | |
| | | df_processed[weight_col] = df_processed[weight_col].ffill().bfill() |
| | | df_processed['smoothed_weight'] = df_processed[weight_col] |
| | | df_processed['rolling_std'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).std() |
| | | df_processed['rolling_mean'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).mean() |
| | | |
| | | return df_processed |
| | | |
| | | def detect_steady_state(self, df, weight_col='smoothed_weight', window_size=20, std_threshold=0.5, duration_threshold=60): |
| | | if df is None or df.empty: |
| | | return df, [] |
| | | |
| | | df['time'] = pd.to_datetime(df['time']) |
| | | df['time_diff'] = df['time'].diff().dt.total_seconds().fillna(0) |
| | | df['is_steady'] = 0 |
| | | |
| | | df['window_std'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).std() |
| | | df['window_mean'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).mean() |
| | | df['fluctuation_range'] = (df['window_std'] / df['window_mean']) * 100 |
| | | df['fluctuation_range'] = df['fluctuation_range'].fillna(0) |
| | | |
| | | df.loc[(df['fluctuation_range'] < std_threshold) & (df['smoothed_weight'] >= 0.1), 'is_steady'] = 1 |
| | | |
| | | steady_segments = [] |
| | | current_segment = {} |
| | | |
| | | for i, row in df.iterrows(): |
| | | if row['is_steady'] == 1: |
| | | if not current_segment: |
| | | current_segment = { |
| | | 'start_time': row['time'], |
| | | 'start_idx': i, |
| | | 'weights': [row['smoothed_weight']] |
| | | } |
| | | else: |
| | | current_segment['weights'].append(row['smoothed_weight']) |
| | | else: |
| | | if current_segment: |
| | | current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time'] |
| | | current_segment['end_idx'] = i-1 |
| | | duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds() |
| | | |
| | | if duration >= duration_threshold: |
| | | weights_array = np.array(current_segment['weights']) |
| | | current_segment['duration'] = duration |
| | | current_segment['mean_weight'] = np.mean(weights_array) |
| | | current_segment['std_weight'] = np.std(weights_array) |
| | | current_segment['min_weight'] = np.min(weights_array) |
| | | current_segment['max_weight'] = np.max(weights_array) |
| | | current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100 |
| | | |
| | | confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50 |
| | | confidence = max(50, min(100, confidence)) |
| | | current_segment['confidence'] = confidence |
| | | |
| | | steady_segments.append(current_segment) |
| | | |
| | | current_segment = {} |
| | | |
| | | if current_segment: |
| | | current_segment['end_time'] = df['time'].iloc[-1] |
| | | current_segment['end_idx'] = len(df) - 1 |
| | | duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds() |
| | | |
| | | if duration >= duration_threshold: |
| | | weights_array = np.array(current_segment['weights']) |
| | | current_segment['duration'] = duration |
| | | current_segment['mean_weight'] = np.mean(weights_array) |
| | | current_segment['std_weight'] = np.std(weights_array) |
| | | current_segment['min_weight'] = np.min(weights_array) |
| | | current_segment['max_weight'] = np.max(weights_array) |
| | | current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100 |
| | | |
| | | confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50 |
| | | confidence = max(50, min(100, confidence)) |
| | | current_segment['confidence'] = confidence |
| | | |
| | | steady_segments.append(current_segment) |
| | | |
| | | for segment in steady_segments: |
| | | df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1 |
| | | |
| | | return df, steady_segments |
| | | |
| | | def get_steady_state_metrics(self, steady_segments): |
| | | if not steady_segments: |
| | | return {} |
| | | |
| | | avg_duration = np.mean([seg['duration'] for seg in steady_segments]) |
| | | avg_fluctuation = np.mean([seg['fluctuation_range'] for seg in steady_segments]) |
| | | avg_confidence = np.mean([seg['confidence'] for seg in steady_segments]) |
| | | total_steady_duration = sum([seg['duration'] for seg in steady_segments]) |
| | | |
| | | return { |
| | | 'total_steady_segments': len(steady_segments), |
| | | 'average_steady_duration': avg_duration, |
| | | 'average_fluctuation_range': avg_fluctuation, |
| | | 'average_confidence': avg_confidence, |
| | | 'total_steady_duration': total_steady_duration |
| | | } |
| | | |
| | | def show_metered_weight_correlation(): |
| | | # 初始化服务 |
| | |
| | | |
| | | # 页面标题 |
| | | st.title("米重相关性分析") |
| | | |
| | | # 初始化服务 |
| | | extruder_service = ExtruderService() |
| | | main_process_service = MainProcessService() |
| | | steady_state_detector = SteadyStateDetector() |
| | | |
| | | # 初始化会话状态用于日期同步 |
| | | if 'mc_start_date' not in st.session_state: |
| | |
| | | st.session_state['mc_quick_select'] = "最近7天" |
| | | if 'mc_time_offset' not in st.session_state: |
| | | st.session_state['mc_time_offset'] = 0.0 |
| | | |
| | | # 初始化稳态识别相关参数 |
| | | if 'mc_ss_window_size' not in st.session_state: |
| | | st.session_state['mc_ss_window_size'] = 20 |
| | | if 'mc_ss_std_threshold' not in st.session_state: |
| | | st.session_state['mc_ss_std_threshold'] = 1.5 |
| | | if 'mc_ss_duration_threshold' not in st.session_state: |
| | | st.session_state['mc_ss_duration_threshold'] = 60 |
| | | if 'mc_use_steady_only' not in st.session_state: |
| | | st.session_state['mc_use_steady_only'] = False |
| | | |
| | | # 定义回调函数 |
| | | def update_dates(qs): |
| | |
| | | st.session_state['mc_time_offset'] = time_offset |
| | | with offset_cols[2]: |
| | | st.write(f"当前偏移: {time_offset} 分钟") |
| | | |
| | | # 稳态参数配置 |
| | | st.markdown("---") |
| | | steady_state_cols = st.columns(4) |
| | | |
| | | with steady_state_cols[0]: |
| | | st.write("⚙️ **稳态参数配置**") |
| | | window_size = st.slider( |
| | | "滑动窗口大小 (秒)", |
| | | min_value=5, |
| | | max_value=60, |
| | | value=st.session_state['mc_ss_window_size'], |
| | | step=5, |
| | | key="mc_ss_window_size", |
| | | help="用于平滑数据和计算统计特征的滑动窗口大小" |
| | | ) |
| | | |
| | | with steady_state_cols[1]: |
| | | st.write("📏 **波动阈值配置**") |
| | | std_threshold = st.slider( |
| | | "标准差阈值", |
| | | min_value=0.1, |
| | | max_value=2.0, |
| | | value=st.session_state['mc_ss_std_threshold'], |
| | | step=0.1, |
| | | key="mc_ss_std_threshold", |
| | | help="米重波动的标准差阈值,低于此值视为稳态" |
| | | ) |
| | | |
| | | with steady_state_cols[2]: |
| | | st.write("⏱️ **持续时间配置**") |
| | | duration_threshold = st.slider( |
| | | "稳态持续时间 (秒)", |
| | | min_value=30, |
| | | max_value=300, |
| | | value=st.session_state['mc_ss_duration_threshold'], |
| | | step=10, |
| | | key="mc_ss_duration_threshold", |
| | | help="稳态持续的最小时间,低于此值不视为稳态段" |
| | | ) |
| | | |
| | | with steady_state_cols[3]: |
| | | st.write("🎯 **分析数据选择**") |
| | | use_steady_only = st.checkbox( |
| | | "仅使用稳态数据", |
| | | value=st.session_state['mc_use_steady_only'], |
| | | key="mc_use_steady_only", |
| | | help="勾选后,相关性分析仅使用识别出的稳态数据" |
| | | ) |
| | | |
| | | # 转换为datetime对象 |
| | | start_dt = datetime.combine(start_date, datetime.min.time()) |
| | |
| | | |
| | | return df_merged |
| | | |
| | | # 执行稳态识别 |
| | | df_extruder_steady, steady_segments = None, [] |
| | | if df_extruder_filtered is not None and not df_extruder_filtered.empty: |
| | | # 数据预处理 |
| | | df_processed = steady_state_detector.preprocess_data( |
| | | df_extruder_filtered, |
| | | weight_col='metered_weight', |
| | | window_size=st.session_state['mc_ss_window_size'] |
| | | ) |
| | | |
| | | # 稳态检测 |
| | | df_extruder_steady, steady_segments = steady_state_detector.detect_steady_state( |
| | | df_processed, |
| | | weight_col='smoothed_weight', |
| | | window_size=st.session_state['mc_ss_window_size'], |
| | | std_threshold=st.session_state['mc_ss_std_threshold'], |
| | | duration_threshold=st.session_state['mc_ss_duration_threshold'] |
| | | ) |
| | | |
| | | # 将稳态标记添加到df_extruder_filtered中,以便在趋势图中使用 |
| | | df_extruder_filtered = df_extruder_filtered.merge( |
| | | df_extruder_steady[['time', 'is_steady', 'smoothed_weight', 'fluctuation_range']], |
| | | on='time', |
| | | how='left' |
| | | ) |
| | | |
| | | # 执行数据整合 |
| | | df_analysis = integrate_data(df_extruder_filtered, df_main_speed, df_temp) |
| | | |
| | |
| | | |
| | | # 重命名米重列 |
| | | df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True) |
| | | |
| | | # 如果选择仅使用稳态数据,过滤掉非稳态数据 |
| | | if st.session_state['mc_use_steady_only']: |
| | | # 确保df_analysis包含is_steady列 |
| | | if 'is_steady' not in df_analysis.columns: |
| | | # 将稳态标记合并到分析数据中 |
| | | df_analysis = df_analysis.merge( |
| | | df_extruder_steady[['time', 'is_steady']], |
| | | on='time', |
| | | how='left' |
| | | ) |
| | | # 过滤稳态数据 |
| | | df_analysis = df_analysis[df_analysis['is_steady'] == 1] |
| | | if df_analysis.empty: |
| | | st.warning("未找到稳态数据,请调整稳态参数或取消'仅使用稳态数据'选项。") |
| | | return |
| | | |
| | | # 缓存稳态数据到会话状态 |
| | | st.session_state['cached_steady_segments'] = steady_segments |
| | | st.session_state['cached_extruder_steady'] = df_extruder_steady |
| | | |
| | | # --- 原始数据趋势图 --- |
| | | st.subheader("📈 原始数据趋势图") |
| | |
| | | yaxis='y5' |
| | | )) |
| | | |
| | | # 添加稳态区域标记 |
| | | for segment in steady_segments: |
| | | # 获取米重数据的y轴范围,用于确定矩形高度 |
| | | y_min = df_extruder_filtered['metered_weight'].min() * 0.95 |
| | | y_max = df_extruder_filtered['metered_weight'].max() * 1.05 |
| | | |
| | | fig_trend.add_shape( |
| | | type="rect", |
| | | x0=segment['start_time'], |
| | | y0=y_min, |
| | | x1=segment['end_time'], |
| | | y1=y_max, |
| | | fillcolor="rgba(0, 255, 0, 0.2)", |
| | | line=dict(color="rgba(0, 200, 0, 0.5)", width=1), |
| | | name="稳态区域", |
| | | layer="below" |
| | | ) |
| | | |
| | | # 配置趋势图布局 |
| | | fig_trend.update_layout( |
| | | title=f'原始数据趋势 (米重向前偏移 {st.session_state["mc_time_offset"]} 分钟)', |
| | |
| | | # 显示趋势图 |
| | | selection = st.plotly_chart(fig_trend, width='stretch', config={'scrollZoom': True}, on_select='rerun' ) |
| | | |
| | | # 稳态统计指标 |
| | | st.subheader("📊 稳态识别统计") |
| | | steady_metrics = steady_state_detector.get_steady_state_metrics(steady_segments) |
| | | metrics_cols = st.columns(5) |
| | | |
| | | with metrics_cols[0]: |
| | | st.metric( |
| | | "稳态段总数", |
| | | steady_metrics.get('total_steady_segments', 0), |
| | | help="识别到的稳态段数量" |
| | | ) |
| | | |
| | | with metrics_cols[1]: |
| | | st.metric( |
| | | "平均稳态时长", |
| | | f"{steady_metrics.get('average_steady_duration', 0):.2f} 秒", |
| | | help="所有稳态段的平均持续时间" |
| | | ) |
| | | |
| | | with metrics_cols[2]: |
| | | st.metric( |
| | | "平均波动范围", |
| | | f"{steady_metrics.get('average_fluctuation_range', 0):.2f}%", |
| | | help="稳态段内米重的平均波动范围(相对于均值的百分比)" |
| | | ) |
| | | |
| | | with metrics_cols[3]: |
| | | st.metric( |
| | | "平均置信度", |
| | | f"{steady_metrics.get('average_confidence', 0):.1f}%", |
| | | help="稳态识别结果的平均置信度" |
| | | ) |
| | | |
| | | with metrics_cols[4]: |
| | | st.metric( |
| | | "总稳态时长", |
| | | f"{steady_metrics.get('total_steady_duration', 0)/60:.2f} 分钟", |
| | | help="所有稳态段的总持续时间" |
| | | ) |
| | | |
| | | # --- 稳态数据趋势图 --- |
| | | st.subheader("📊 稳态数据趋势图") |
| | | |
| | | # 创建稳态数据趋势图 |
| | | if df_extruder_steady is not None and not df_extruder_steady.empty: |
| | | fig_steady = go.Figure() |
| | | |
| | | # 添加原始米重数据 |
| | | fig_steady.add_trace(go.Scatter( |
| | | x=df_extruder_steady['time'], |
| | | y=df_extruder_steady['metered_weight'], |
| | | name='原始米重', |
| | | mode='lines', |
| | | opacity=0.6, |
| | | line=dict(color='lightblue', width=1) |
| | | )) |
| | | |
| | | # 添加平滑后的米重数据 |
| | | fig_steady.add_trace(go.Scatter( |
| | | x=df_extruder_steady['time'], |
| | | y=df_extruder_steady['smoothed_weight'], |
| | | name='平滑米重', |
| | | mode='lines', |
| | | line=dict(color='blue', width=2) |
| | | )) |
| | | |
| | | # 添加波动范围(作为面积图) |
| | | fig_steady.add_trace(go.Scatter( |
| | | x=df_extruder_steady['time'], |
| | | y=df_extruder_steady['metered_weight'] + df_extruder_steady['rolling_std'], |
| | | name='波动上限', |
| | | mode='lines', |
| | | line=dict(color='rgba(255,0,0,0)'), |
| | | showlegend=True |
| | | )) |
| | | |
| | | fig_steady.add_trace(go.Scatter( |
| | | x=df_extruder_steady['time'], |
| | | y=df_extruder_steady['metered_weight'] - df_extruder_steady['rolling_std'], |
| | | name='波动下限', |
| | | mode='lines', |
| | | line=dict(color='rgba(255,0,0,0)'), |
| | | fill='tonexty', |
| | | fillcolor='rgba(255,0,0,0.1)' |
| | | )) |
| | | |
| | | # 添加稳态标记 |
| | | fig_steady.add_trace(go.Scatter( |
| | | x=df_extruder_steady['time'], |
| | | y=df_extruder_steady['is_steady'] * (df_extruder_steady['metered_weight'].max() * 1.1), |
| | | name='稳态标记', |
| | | mode='lines', |
| | | line=dict(color='green', width=1, dash='dash') |
| | | )) |
| | | |
| | | # 配置稳态数据趋势图布局 |
| | | fig_steady.update_layout( |
| | | title="稳态数据趋势分析", |
| | | xaxis=dict( |
| | | title='时间', |
| | | rangeslider=dict(visible=True), |
| | | type='date' |
| | | ), |
| | | yaxis=dict( |
| | | title='米重 (Kg/m)', |
| | | title_font=dict(color='blue'), |
| | | tickfont=dict(color='blue') |
| | | ), |
| | | yaxis2=dict( |
| | | title='稳态标记', |
| | | title_font=dict(color='green'), |
| | | tickfont=dict(color='green'), |
| | | overlaying='y', |
| | | side='right', |
| | | range=[-0.1, 1.1], |
| | | showgrid=False |
| | | ), |
| | | legend=dict( |
| | | orientation="h", |
| | | yanchor="bottom", |
| | | y=1.02, |
| | | xanchor="right", |
| | | x=1 |
| | | ), |
| | | height=500, |
| | | margin=dict(l=100, r=100, t=100, b=100), |
| | | hovermode='x unified' |
| | | ) |
| | | |
| | | # 显示稳态数据趋势图 |
| | | st.plotly_chart(fig_steady, width='stretch', config={'scrollZoom': True}) |
| | | |
| | | # --- 稳态参数相关性趋势图 --- |
| | | st.subheader("📈 稳态参数相关性趋势图") |
| | | |
| | | if df_analysis is not None and not df_analysis.empty: |
| | | # 创建稳态参数相关性趋势图 |
| | | fig_steady_corr = go.Figure() |
| | | |
| | | # 选择相关系数最高的前3个参数 |
| | | if '米重' in df_analysis.columns: |
| | | # 计算各参数与米重的相关系数 |
| | | corr_with_weight = df_analysis.corr()[['米重']].sort_values('米重', ascending=False) |
| | | top_params = corr_with_weight.index[1:4] # 排除米重本身,取前3个 |
| | | |
| | | # 添加米重数据 |
| | | fig_steady_corr.add_trace(go.Scatter( |
| | | x=df_analysis['time'], |
| | | y=df_analysis['米重'], |
| | | name='米重', |
| | | mode='lines', |
| | | line=dict(color='blue', width=2), |
| | | yaxis='y' |
| | | )) |
| | | |
| | | # 添加相关参数数据 |
| | | colors = ['red', 'green', 'orange'] |
| | | for i, param in enumerate(top_params): |
| | | if param in df_analysis.columns: |
| | | fig_steady_corr.add_trace(go.Scatter( |
| | | x=df_analysis['time'], |
| | | y=df_analysis[param], |
| | | name=param, |
| | | mode='lines', |
| | | line=dict(color=colors[i], width=1.5), |
| | | yaxis=f'y{i+2}' |
| | | )) |
| | | |
| | | # 配置图表布局 |
| | | layout = { |
| | | 'title': f'米重与相关参数趋势(前3个相关参数)', |
| | | 'xaxis': { |
| | | 'title': '时间', |
| | | 'rangeslider': dict(visible=True), |
| | | 'type': 'date' |
| | | }, |
| | | 'yaxis': { |
| | | 'title': '米重 (Kg/m)', |
| | | 'title_font': dict(color='blue'), |
| | | 'tickfont': dict(color='blue') |
| | | }, |
| | | 'legend': { |
| | | 'orientation': "h", |
| | | 'yanchor': "bottom", |
| | | 'y': 1.02, |
| | | 'xanchor': "right", |
| | | 'x': 1 |
| | | }, |
| | | 'height': 600, |
| | | 'margin': dict(l=100, r=200, t=100, b=100), |
| | | 'hovermode': 'x unified' |
| | | } |
| | | |
| | | # 添加额外的y轴配置 |
| | | for i, param in enumerate(top_params): |
| | | layout[f'yaxis{i+2}'] = { |
| | | 'title': param, |
| | | 'title_font': dict(color=colors[i]), |
| | | 'tickfont': dict(color=colors[i]), |
| | | 'overlaying': 'y', |
| | | 'side': 'right', |
| | | 'anchor': 'free', |
| | | 'position': 1 - (i+1)*0.15 |
| | | } |
| | | |
| | | fig_steady_corr.update_layout(layout) |
| | | st.plotly_chart(fig_steady_corr, width='stretch', config={'scrollZoom': True}) |
| | | |
| | | # 调试输出 |
| | | # st.write("原始 selection 对象:", selection) |
| | | |