2025-06-30 09:53:49 +08:00

281 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.font_manager import FontProperties
import json
from matplotlib.patches import Circle, Ellipse
import matplotlib.transforms as transforms
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import time
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 路径类型映射
PATH_MAPPING = {
'cycloid': '摆线',
'point': '定点',
'point_rub': '定点',
'line': '直线',
'in_spiral': '螺旋线',
'out_spiral': '螺旋线'
}
# 路径颜色映射
PATH_COLORS = {
'cycloid': 'blue',
'point': 'red',
'point_rub': 'red',
'line': 'green',
'in_spiral': 'purple',
'out_spiral': 'purple'
}
# 定义网格和穴位坐标
grid_size = (7, 25)
acupoints = {
"崇骨": (4, 25), "大椎": (4, 24), "肩中左俞": (3, 23), "肩中右俞": (5, 23),
"肩外左俞": (2, 22), "肩外右俞": (6, 22), "秉风左": (1, 21), "曲垣左": (2, 21),
"大杼左": (3, 21), "陶道": (4, 21), "大杼右": (5, 21), "曲垣右": (6, 21),
"秉风右": (7, 21), "附分左": (2, 20), "风门左": (3, 20), "风门右": (5, 20),
"附分右": (6, 20), "魄户左": (2, 19), "肺俞左": (3, 19), "身柱": (4, 19),
"肺俞右": (5, 19), "魄户右": (6, 19), "天宗左": (1, 18), "膏肓左": (2, 18),
"厥阴左俞": (3, 18), "厥阴右俞": (5, 18), "膏肓右": (6, 18), "天宗右": (7, 18),
"神堂左": (2, 17), "心俞左": (3, 17), "神道": (4, 17), "心俞右": (5, 17),
"神堂右": (6, 17), "譩譆左": (2, 16), "督俞左": (3, 16), "灵台": (4, 16),
"督俞右": (5, 16), "譩譆右": (6, 16), "膈关左": (2, 15), "膈俞左": (3, 15),
"至阳": (4, 15), "膈俞右": (5, 15), "膈关右": (6, 15), "魂门左": (2, 14),
"肝俞左": (3, 14), "筋缩": (4, 14), "肝俞右": (5, 14), "魂门右": (6, 14),
"阳纲左": (2, 13), "胆俞左": (3, 13), "中枢": (4, 13), "胆俞右": (5, 13),
"阳纲右": (6, 13), "意舍左": (2, 12), "脾俞左": (3, 12), "脊中": (4, 12),
"脾俞右": (5, 12), "意舍右": (6, 12), "胃仓左": (2, 11), "胃俞左": (3, 11),
"胃俞右": (5, 11), "胃仓右": (6, 11), "肓门左": (2, 10), "三焦左俞": (3, 10),
"悬枢": (4, 10), "三焦右俞": (5, 10), "肓门右": (6, 10), "京门左": (1, 9),
"志室左": (2, 9), "肾俞左": (3, 9), "命门": (4, 9), "肾俞右": (5, 9),
"志室右": (6, 9), "京门右": (7, 9), "气海左俞": (3, 8), "气海右俞": (5, 8),
"大肠左俞": (3, 7), "腰阳关": (4, 7), "大肠右俞": (5, 7), "关元左俞": (3, 6),
"关元右俞": (5, 6), "小肠左俞": (3, 5), "小肠右俞": (5, 5), "胞肓左": (2, 4),
"膀胱左俞": (3, 4), "膀胱右俞": (5, 4), "胞肓右": (6, 4), "中膂左俞": (3, 3),
"中膂右俞": (5, 3), "秩边左": (2, 2), "白环左俞": (3, 2), "白环右俞": (5, 2),
"秩边右": (6, 2), "会阳左": (3, 1), "会阳右": (5, 1)
}
def read_json_file(file_path):
"""读取并解析JSON文件"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
return json.load(file)
except FileNotFoundError:
print(f"错误:文件 {file_path} 未找到")
return None
except json.JSONDecodeError:
print("错误文件不是有效的JSON格式")
return None
def display_all_therapies(therapy_data):
"""
显示所有疗法信息
:param therapy_data: 包含所有疗法数据的字典
"""
if not therapy_data:
return
for therapy_name, therapy_info in therapy_data.items():
print("=" * 60)
print(f"疗法名称: {therapy_name}")
print(f"介绍: {therapy_info['introduction']}")
print(f"身体部位: {therapy_info['body_part']}")
print(f"选择的任务: {therapy_info['choose_task']}")
print("\n任务计划:")
for i, task in enumerate(therapy_info['task_plan'], 1):
print(f"{i}. 从 {task['start_point']}{task['end_point']},路径类型: {task['path']}")
print(f"\n总步骤数: {len(therapy_info['task_plan'])}")
print("=" * 60 + "\n")
def parse_point(point_str, default_x=4, default_y=1):
"""
解析穴位点字符串,处理组合点和比例点
规则:
- "A" -> A穴位的坐标
- "A&B" -> A和B的中点
- "A&B@ratio" -> AB连线上距离A比例为ratio的点 (ratio可以是小数或分数如0.3或1/3)
"""
def parse_ratio(ratio_str):
"""解析比例字符串,支持小数和分数形式"""
try:
if '/' in ratio_str:
numerator, denominator = ratio_str.split('/')
return float(numerator) / float(denominator)
return float(ratio_str)
except:
return 0.5 # 默认返回中点
try:
if '&' in point_str:
# 处理组合点
if '@' in point_str:
# 带比例的组合点 (A&B@ratio)
points_part, ratio_str = point_str.split('@')
ratio = parse_ratio(ratio_str)
points = points_part.split('&')
if len(points) != 2:
raise ValueError(f"组合点格式错误: {point_str}")
# 获取两个穴位的坐标
point_a = parse_point(points[0])
point_b = parse_point(points[1])
# 计算比例点
x = point_a[0] + ratio * (point_b[0] - point_a[0])
y = point_a[1] + ratio * (point_b[1] - point_a[1])
return (x, y)
else:
# 简单中点 (A&B)
points = point_str.split('&')
coords = [parse_point(p) for p in points]
x = sum(c[0] for c in coords) / len(coords)
y = sum(c[1] for c in coords) / len(coords)
return (x, y)
elif point_str in acupoints:
# 单个穴位点
return acupoints[point_str]
else:
# 未知穴位,返回默认位置
return (default_x, default_y)
except Exception as e:
print(f"解析穴位点错误: {point_str}, 错误: {e}")
return (default_x, default_y)
def draw_cycloid(ax, start, end, color='blue'):
"""绘制摆线轨迹"""
x1, y1 = start
x2, y2 = end
# 计算控制点
ctrl_x = (x1 + x2) / 2
ctrl_y = (y1 + y2) / 2 + abs(x2 - x1) * 0.5
# 创建贝塞尔曲线
t = np.linspace(0, 1, 100)
x = (1-t)**2 * x1 + 2 * (1-t) * t * ctrl_x + t**2 * x2
y = (1-t)**2 * y1 + 2 * (1-t) * t * ctrl_y + t**2 * y2
ax.plot(x, y, color=color, linestyle='-', linewidth=2)
def draw_spiral(ax, center, size=0.5, color='purple', clockwise=True):
"""绘制螺旋线轨迹"""
t = np.linspace(0, 2*np.pi, 100)
r = np.linspace(0, size, 100)
if not clockwise:
t = -t
x = center[0] + r * np.cos(t)
y = center[1] + r * np.sin(t)
ax.plot(x, y, color=color, linestyle='-', linewidth=2)
def draw_point(ax, point, color='red'):
"""绘制定点轨迹"""
circle = Circle(point, radius=0.2, color=color, alpha=0.7)
ax.add_patch(circle)
def draw_line(ax, start, end, color='green'):
"""绘制直线轨迹"""
ax.plot([start[0], end[0]], [start[1], end[1]],
color=color, linestyle='-', linewidth=2)
def create_legend(ax):
"""创建图例"""
legend_elements = []
for path_type, color in PATH_COLORS.items():
if path_type in PATH_MAPPING:
legend_elements.append(
plt.Line2D([0], [0], color=color, lw=2,
label=f'{PATH_MAPPING[path_type]} ({path_type})'))
legend_elements.extend([
plt.Line2D([0], [0], marker='o', color='k', lw=0, label='起点'),
plt.Line2D([0], [0], marker='x', color='k', lw=0, label='终点')
])
ax.legend(handles=legend_elements, loc='upper right', bbox_to_anchor=(1.3, 1))
def visualize_task_steps_sequentially(json_path, therapy_name, step_interval=0.5):
"""
按顺序动态绘制疗法任务的每个步骤
:param json_path: JSON文件路径
:param therapy_name: 疗法名称
:param step_interval: 每个步骤的显示时间(秒)
"""
data = read_json_file(json_path)
if not data or therapy_name not in data:
print(f"疗法 '{therapy_name}' 不存在")
return
therapy_info = data[therapy_name]
task_plan = therapy_info['task_plan']
# 初始化图形
fig, ax = plt.subplots(figsize=(15, 10))
ax.set_xticks(np.arange(0, grid_size[0] + 1, 1))
ax.set_yticks(np.arange(0, grid_size[1] + 1, 1))
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
# 绘制所有穴位(灰色半透明)
for name, (x, y) in acupoints.items():
ax.plot(x, y, 'o', color='gray', markersize=8, alpha=0.2)
ax.text(x, y, name, ha='center', va='center', fontsize=6, alpha=0.3)
# 动态绘制每个步骤
for step_idx, task in enumerate(task_plan, 1):
start = parse_point(task['start_point'])
end = parse_point(task['end_point'])
path_type = task['path']
# 清除上一步的临时图形(保留穴位和已完成的步骤)
for artist in ax.lines + ax.patches + ax.texts:
if hasattr(artist, '_is_temp') and artist._is_temp:
artist.remove()
# 绘制当前步骤
if path_type in ['cycloid']:
draw_cycloid(ax, start, end, PATH_COLORS[path_type])
elif path_type in ['point', 'point_rub']:
draw_point(ax, start, PATH_COLORS[path_type])
elif path_type == 'line':
draw_line(ax, start, end, PATH_COLORS[path_type])
elif path_type in ['in_spiral', 'out_spiral']:
draw_spiral(ax, start, color=PATH_COLORS[path_type], clockwise=(path_type == 'in_spiral'))
# 标记当前步骤的起点和终点(临时标记)
start_marker, = ax.plot(start[0], start[1], 'ko', markersize=10, alpha=0.7)
end_marker, = ax.plot(end[0], end[1], 'kx', markersize=10, alpha=0.7)
step_text = ax.text((start[0]+end[0])/2, (start[1]+end[1])/2,
str(step_idx), fontsize=12, ha='center', va='center',
bbox=dict(facecolor='white', alpha=0.8))
# 标记为临时对象(下次循环会被清除)
for artist in [start_marker, end_marker, step_text]:
artist._is_temp = True
# 更新标题
ax.set_title(
f"{therapy_name}\n"
f"步骤 {step_idx}/{len(task_plan)}: {task['start_point']}{task['end_point']}\n"
f"类型: {PATH_MAPPING.get(path_type, path_type)}",
fontsize=12
)
plt.draw()
plt.pause(step_interval) # 暂停一段时间
plt.show()
# 使用示例
if __name__ == "__main__":
json_path = "C:/Users/ZIWEI/Documents/work/向量化/cur_plans_hzw.json"
data = read_json_file(json_path)
display_all_therapies(data)
visualize_task_steps_sequentially(json_path,"点振波理疗头-肩颈-默认")