pose-platform 1.24.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. capabilities/mcp_server.py +239 -0
  2. capabilities/tools/_motion_angle_profiles.py +467 -0
  3. capabilities/tools/_running_standards.py +495 -0
  4. capabilities/tools/_session_context.py +228 -0
  5. capabilities/tools/action_boundaries_tool.py +220 -0
  6. capabilities/tools/analyze_motion_angles_tool.py +831 -0
  7. capabilities/tools/analyze_running_gait_tool.py +602 -0
  8. capabilities/tools/base.py +191 -0
  9. capabilities/tools/energy_tool.py +132 -0
  10. capabilities/tools/highlight_tool.py +264 -0
  11. capabilities/tools/kinematics.py +235 -0
  12. capabilities/tools/loader.py +693 -0
  13. capabilities/tools/motion_segments_tool.py +371 -0
  14. capabilities/tools/registry.py +190 -0
  15. capabilities/tools/running_speed_tool.py +820 -0
  16. capabilities/tools/score_running_efficiency_tool.py +652 -0
  17. capabilities/tools/score_sprint_tool.py +493 -0
  18. capabilities/tools/sensor_loader.py +257 -0
  19. capabilities/tools/set_runner_profile_tool.py +126 -0
  20. capabilities/tools/set_session_context_tool.py +158 -0
  21. capabilities/tools/stats_tool.py +397 -0
  22. capabilities/tools/velocity_tool.py +291 -0
  23. capabilities/utils/__init__.py +0 -0
  24. capabilities/utils/motion_energy.py +74 -0
  25. data_hub/__init__.py +5 -0
  26. data_hub/catalog/__init__.py +0 -0
  27. data_hub/catalog/dataset_builder.py +144 -0
  28. data_hub/pipeline/__init__.py +0 -0
  29. data_hub/pipeline/bdy_decoder.py +80 -0
  30. data_hub/pipeline/ingest.py +673 -0
  31. data_hub/schema/__init__.py +0 -0
  32. data_hub/schema/dataset.py +77 -0
  33. data_hub/schema/motion_record.py +189 -0
  34. pose_platform-1.24.0.dist-info/METADATA +187 -0
  35. pose_platform-1.24.0.dist-info/RECORD +59 -0
  36. pose_platform-1.24.0.dist-info/WHEEL +5 -0
  37. pose_platform-1.24.0.dist-info/entry_points.txt +2 -0
  38. pose_platform-1.24.0.dist-info/top_level.txt +4 -0
  39. pose_platform_core/__init__.py +6 -0
  40. pose_platform_core/cache_manager.py +156 -0
  41. pose_platform_core/config.py +211 -0
  42. pose_platform_core/gap_registry.py +195 -0
  43. pose_platform_core/llm_client.py +384 -0
  44. pose_platform_core/llm_protocol_adapter.py +202 -0
  45. pose_platform_core/llm_router.py +97 -0
  46. pose_platform_core/locale.py +46 -0
  47. pose_platform_core/paths.py +34 -0
  48. pose_platform_core/product_interface.py +102 -0
  49. pose_platform_core/sports_catalog.py +38 -0
  50. pose_platform_core/telemetry.py +53 -0
  51. pose_platform_core/text_sanitize.py +21 -0
  52. pose_platform_core/version_manager.py +185 -0
  53. products/__init__.py +0 -0
  54. products/pose_cli/__init__.py +0 -0
  55. products/pose_cli/cli.py +1299 -0
  56. products/pose_cli/cmd_data.py +117 -0
  57. products/pose_cli/cmd_telemetry.py +85 -0
  58. products/pose_cli/locale_text.py +267 -0
  59. products/pose_cli/product.py +28 -0
@@ -0,0 +1,239 @@
1
+ """
2
+ capabilities/mcp_server.py
3
+ 统一 MCP Server — 所有产品线连接同一个服务端
4
+
5
+ 启动: python -m capabilities.mcp_server
6
+ 注册: .claude/settings.json 中 mcpServers.pose-analysis
7
+ """
8
+ from __future__ import annotations
9
+ import json, os, glob
10
+ from pathlib import Path
11
+
12
+ DATA_DIR = Path(os.environ.get("POSE_DATA_DIR", "./data"))
13
+
14
+ try:
15
+ from mcp.server.fastmcp import FastMCP
16
+ mcp = FastMCP("pose-analysis")
17
+ MCP_AVAILABLE = True
18
+ except ImportError:
19
+ mcp = None
20
+ MCP_AVAILABLE = False
21
+ print("[MCP] fastmcp 未安装,MCP Server 以 stub 模式运行(功能测试仍可用)")
22
+
23
+
24
+ def _run_tool(tool_name: str, tool_input: dict) -> str:
25
+ from capabilities.tools.registry import registry
26
+ import tempfile
27
+ tool = registry.get(tool_name)
28
+ if tool is None:
29
+ return json.dumps({"error": f"Tool {tool_name!r} 未注册"})
30
+ outdir = Path(tempfile.mkdtemp())
31
+ return tool.safe_run(tool_input, outdir)
32
+
33
+
34
+ if MCP_AVAILABLE:
35
+ # 仅向 MCP 暴露在 registry 中已注册的 tool(受 POSE_ENABLE_WIP_TOOLS 门禁约束)
36
+ # wip tool 在 prod 部署中不出现在客户端工具列表,避免暴露未稳定能力
37
+ from capabilities.tools.registry import registry as _registry
38
+ _registered_names = set(_registry.names())
39
+
40
+ def _maybe_tool(tool_name: str):
41
+ """条件装饰器:仅当 tool_name 在 registry 中才用 @mcp.tool() 注册。
42
+
43
+ 若 tool 是 wip 且当前环境不开启(POSE_ENABLE_WIP_TOOLS != "1"),返回 no-op 装饰器,
44
+ 函数仍定义在 module 里但不向 MCP server 暴露。"""
45
+ if tool_name in _registered_names:
46
+ return mcp.tool()
47
+ return lambda fn: fn
48
+
49
+ @mcp.resource("pose://sessions/{filename}")
50
+ def get_session(filename: str) -> str:
51
+ path = DATA_DIR / filename
52
+ return path.read_text(encoding="utf-8") if path.exists() else f"文件不存在: {filename}"
53
+
54
+ @mcp.resource("pose://sessions/index")
55
+ def list_sessions() -> str:
56
+ files = sorted(DATA_DIR.glob("*.csv")) if DATA_DIR.exists() else []
57
+ return json.dumps([{"filename": f.name, "size_kb": f.stat().st_size // 1024}
58
+ for f in files], ensure_ascii=False)
59
+
60
+ @_maybe_tool("detect_peak_energy")
61
+ def detect_peak_energy(path: str, fps: float = 30.0,
62
+ prominence: float = 0.1) -> dict:
63
+ """检测运动峰值能量时刻,自动排除抖动帧"""
64
+ return json.loads(_run_tool("detect_peak_energy",
65
+ {"path": path, "fps": fps, "prominence": prominence}))
66
+
67
+ @_maybe_tool("stats")
68
+ def stats(path: str) -> dict:
69
+ """全面统计分析:运动幅度、数据质量、关节角度范围"""
70
+ return json.loads(_run_tool("stats", {"path": path}))
71
+
72
+ @_maybe_tool("detect_velocity")
73
+ def detect_velocity(path: str, joint: str = "body_position",
74
+ smooth: bool = True) -> dict:
75
+ """计算关节移动速度(默认 body_position 世界坐标,反映全身位移速度)"""
76
+ return json.loads(_run_tool("detect_velocity",
77
+ {"path": path, "joint": joint, "smooth": smooth}))
78
+
79
+ @_maybe_tool("detect_swing_multifeature")
80
+ def detect_swing_multifeature(
81
+ path: str,
82
+ joint: str = "right_hand_joint",
83
+ arm_joint: str = "right_arm_joint",
84
+ shoulder_joint: str = "right_shoulder_1_joint",
85
+ coord: str = "mpoint",
86
+ smooth: bool = True,
87
+ min_hand_speed: float = 6.0,
88
+ min_swing_gap_s: float = 1.5,
89
+ max_swing_width_s: float = 2.5,
90
+ min_arm_engagement: float = 0.14,
91
+ fast_bypass_speed: float = 20.0,
92
+ min_shoulder_speed: float = 0.85,
93
+ shoulder_check_threshold: float = 8.0,
94
+ low_speed_arm_threshold: float = 0.22,
95
+ low_speed_shou_threshold: float = 0.75,
96
+ min_dir_dot: float = 0.95,
97
+ include_hour: bool = False,
98
+ ) -> dict:
99
+ """多关节挥拍检测:输出每次挥拍及其对应引拍谷底时刻,过滤假阳性(v1.1.0)
100
+ 每条 swing 记录新增 backswing_frame/backswing_time/backswing_s/backswing_speed/backswing_shallow,
101
+ 顶层新增 backswing_count(始终等于 swing_count)。"""
102
+ return json.loads(_run_tool("detect_swing_multifeature", {
103
+ "path": path,
104
+ "joint": joint,
105
+ "arm_joint": arm_joint,
106
+ "shoulder_joint": shoulder_joint,
107
+ "coord": coord,
108
+ "smooth": smooth,
109
+ "min_hand_speed": min_hand_speed,
110
+ "min_swing_gap_s": min_swing_gap_s,
111
+ "max_swing_width_s": max_swing_width_s,
112
+ "min_arm_engagement": min_arm_engagement,
113
+ "fast_bypass_speed": fast_bypass_speed,
114
+ "min_shoulder_speed": min_shoulder_speed,
115
+ "shoulder_check_threshold": shoulder_check_threshold,
116
+ "low_speed_arm_threshold": low_speed_arm_threshold,
117
+ "low_speed_shou_threshold": low_speed_shou_threshold,
118
+ "min_dir_dot": min_dir_dot,
119
+ "include_hour": include_hour,
120
+ }))
121
+
122
+ @_maybe_tool("classify_stroke_type")
123
+ def classify_stroke_type(
124
+ path: str,
125
+ joint: str = "right_hand_joint",
126
+ arm_joint: str = "right_arm_joint",
127
+ shoulder_joint: str = "right_shoulder_1_joint",
128
+ forearm_joint: str = "right_forearm_joint",
129
+ coord: str = "mpoint",
130
+ smooth: bool = True,
131
+ min_hand_speed: float = 6.0,
132
+ min_swing_gap_s: float = 1.5,
133
+ ) -> dict:
134
+ """羽毛球动作 4 层分类(v1.0.0):输出每拍球路标签(杀球/高远球/平高球/吊球/抽球/推球/挑球/网前球等)及分类依据特征"""
135
+ return json.loads(_run_tool("classify_stroke_type", {
136
+ "path": path,
137
+ "joint": joint,
138
+ "arm_joint": arm_joint,
139
+ "shoulder_joint": shoulder_joint,
140
+ "forearm_joint": forearm_joint,
141
+ "coord": coord,
142
+ "smooth": smooth,
143
+ "min_hand_speed": min_hand_speed,
144
+ "min_swing_gap_s": min_swing_gap_s,
145
+ }))
146
+
147
+ @_maybe_tool("measure_running_speed")
148
+ def measure_running_speed(
149
+ path: str,
150
+ smooth: bool = True,
151
+ coord_mode: str = "horizontal",
152
+ series_resolution: str = "raw",
153
+ age: int | None = None,
154
+ gender: str = "male",
155
+ race_distance_m: int = 100,
156
+ region: str = "cn",
157
+ ) -> dict:
158
+ """测量跑步速度(sport=sprint):输出合速度统计 (m/s & km/h)、时序曲线、数据质量诊断。
159
+ 传入 age + gender + race_distance_m + region 同时输出青少年评级(assessment 字段)。
160
+ coord_mode: horizontal=XZ 平面(默认)/ 3d。
161
+ series_resolution: raw / downsampled(0.2s 间隔)。
162
+ race_distance_m: 100(默认)/ 50(仅 cn region 支持)。
163
+ region: cn=中国教育部+体育总局 / us=USATF Junior Olympics / ca=Athletics Canada Youth。"""
164
+ payload = {
165
+ "path": path,
166
+ "smooth": smooth,
167
+ "coord_mode": coord_mode,
168
+ "series_resolution": series_resolution,
169
+ "gender": gender,
170
+ "race_distance_m": race_distance_m,
171
+ "region": region,
172
+ }
173
+ if age is not None:
174
+ payload["age"] = age
175
+ return json.loads(_run_tool("measure_running_speed", payload))
176
+
177
+ @_maybe_tool("detect_action_boundaries")
178
+ def detect_action_boundaries(
179
+ path: str,
180
+ onset_energy_ratio: float = 0.01,
181
+ max_filter_win_s: float = 0.3,
182
+ prominence: float = 0.05,
183
+ min_action_duration_s: float = 0.3,
184
+ min_peak_gap_s: float = 0.5,
185
+ ) -> dict:
186
+ """通用动作区间检测:从全身位移能量找到动作的 onset/offset 边界,输出 start_s/peak_s/end_s。
187
+ 适用于短跑、跳跃等非羽毛球运动;onset_energy_ratio=0.01(1% 峰值能量)对 sprint 准备阶段覆盖准确。"""
188
+ return json.loads(_run_tool("detect_action_boundaries", {
189
+ "path": path,
190
+ "onset_energy_ratio": onset_energy_ratio,
191
+ "max_filter_win_s": max_filter_win_s,
192
+ "prominence": prominence,
193
+ "min_action_duration_s": min_action_duration_s,
194
+ "min_peak_gap_s": min_peak_gap_s,
195
+ }))
196
+
197
+ @_maybe_tool("detect_highlight")
198
+ def detect_highlight(
199
+ path: str,
200
+ sport_id: str | None = None,
201
+ max_highlights: int = 3,
202
+ onset_energy_ratio: float = 0.01,
203
+ ) -> dict:
204
+ """高光时刻统一入口:自动路由到最优分析路径,返回 highlights[].start_s/peak_s/end_s。
205
+ sport_id=badminton → detect_swing_multifeature;sprint/通用 → detect_action_boundaries;
206
+ 无结果降级到 detect_peak_energy+±0.5s 扩展。"""
207
+ payload: dict = {
208
+ "path": path,
209
+ "max_highlights": max_highlights,
210
+ "onset_energy_ratio": onset_energy_ratio,
211
+ }
212
+ if sport_id is not None:
213
+ payload["sport_id"] = sport_id
214
+ return json.loads(_run_tool("detect_highlight", payload))
215
+
216
+ @_maybe_tool("compare_with_reference")
217
+ def compare_with_reference(path: str, reference_id: str = "") -> dict:
218
+ """与参考标准对比关节偏差"""
219
+ payload: dict = {"path": path}
220
+ if reference_id:
221
+ payload["reference_id"] = reference_id
222
+ return json.loads(_run_tool("compare_with_reference", payload))
223
+
224
+ @mcp.prompt()
225
+ def analyze_session(filename: str) -> str:
226
+ """标准 session 分析流程模板"""
227
+ return f"""
228
+ 请对 session 文件 {filename} 进行完整分析:
229
+ 1. 先用 stats("{filename}") 了解数据结构和质量
230
+ 2. 用 detect_peak_energy("{filename}") 找出运动峰值
231
+ 3. 用中文输出分析报告,包含数据质量摘要、运动峰值时间线、最活跃关节排名
232
+ """
233
+
234
+ if __name__ == "__main__":
235
+ mcp.run()
236
+ else:
237
+ def run_tool_directly(tool_name: str, **kwargs) -> dict:
238
+ """MCP 不可用时的直接调用接口(用于测试)"""
239
+ return json.loads(_run_tool(tool_name, kwargs))