Merge branch 'main' of http://121.37.111.42:3000/ThbTech/BodyBalanceEvaluation
This commit is contained in:
commit
b0132700b9
3
.gitignore
vendored
3
.gitignore
vendored
@ -47,6 +47,7 @@ build/
|
|||||||
frontend/src/renderer/dist/
|
frontend/src/renderer/dist/
|
||||||
frontend/src/renderer/dist-electron/
|
frontend/src/renderer/dist-electron/
|
||||||
backend/data/patients/
|
backend/data/patients/
|
||||||
|
frontend/src/renderer/src/services/
|
||||||
|
|
||||||
# 临时文件
|
# 临时文件
|
||||||
*.tmp
|
*.tmp
|
||||||
@ -21415,3 +21416,5 @@ frontend/src/renderer/dist-electron/win-unpacked/resources/backend/BodyBalanceBa
|
|||||||
frontend/src/renderer/dist-electron/win-unpacked/resources/backend/BodyBalanceBackend/dll/smitsense/SMiTSenseUsbWrapper.dll
|
frontend/src/renderer/dist-electron/win-unpacked/resources/backend/BodyBalanceBackend/dll/smitsense/SMiTSenseUsbWrapper.dll
|
||||||
frontend/src/renderer/dist-electron/win-unpacked/resources/backend/BodyBalanceBackend/dll/smitsense/Wrapper.dll
|
frontend/src/renderer/dist-electron/win-unpacked/resources/backend/BodyBalanceBackend/dll/smitsense/Wrapper.dll
|
||||||
backend/data/patients/202508060001/20250820102556/feet.mp4
|
backend/data/patients/202508060001/20250820102556/feet.mp4
|
||||||
|
frontend/src/renderer/src/services/api.js
|
||||||
|
frontend/src/renderer/src/services/api.js
|
||||||
|
@ -163,7 +163,7 @@ def init_app():
|
|||||||
db_manager.init_database()
|
db_manager.init_database()
|
||||||
|
|
||||||
# 初始化设备管理器(不自动初始化设备)
|
# 初始化设备管理器(不自动初始化设备)
|
||||||
device_manager = DeviceManager(db_manager)
|
device_manager = DeviceManager(db_manager, recording_manager)
|
||||||
|
|
||||||
# 初始化相机管理器
|
# 初始化相机管理器
|
||||||
global camera_manager, recording_manager
|
global camera_manager, recording_manager
|
||||||
@ -902,7 +902,6 @@ def collect_detection_data(session_id):
|
|||||||
# 获取请求数据
|
# 获取请求数据
|
||||||
data = flask_request.get_json() or {}
|
data = flask_request.get_json() or {}
|
||||||
patient_id = data.get('patient_id')
|
patient_id = data.get('patient_id')
|
||||||
screen_image_base64 = data.get('imageData')
|
|
||||||
|
|
||||||
# 如果没有提供patient_id,从会话信息中获取
|
# 如果没有提供patient_id,从会话信息中获取
|
||||||
if not patient_id:
|
if not patient_id:
|
||||||
@ -920,11 +919,10 @@ def collect_detection_data(session_id):
|
|||||||
'error': '无法获取患者ID'
|
'error': '无法获取患者ID'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 调用设备管理器采集数据
|
# 调用录制管理器采集数据
|
||||||
collected_data = device_manager.collect_data(
|
collected_data = recording_manager.collect_detection_data(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
patient_id=patient_id,
|
patient_id=patient_id
|
||||||
screen_image_base64=screen_image_base64
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 将采集的数据保存到数据库
|
# 将采集的数据保存到数据库
|
||||||
|
@ -13,24 +13,37 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .camera_manager import CameraManager
|
from .camera_manager import CameraManager
|
||||||
|
from .femtobolt_manager import FemtoBoltManager
|
||||||
|
from .pressure_manager import PressureManager
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from camera_manager import CameraManager
|
from camera_manager import CameraManager
|
||||||
|
from femtobolt_manager import FemtoBoltManager
|
||||||
|
from pressure_manager import PressureManager
|
||||||
|
|
||||||
class RecordingManager:
|
class RecordingManager:
|
||||||
def __init__(self, camera_manager: Optional[CameraManager] = None, db_manager=None):
|
def __init__(self, camera_manager: Optional[CameraManager] = None, db_manager=None,
|
||||||
|
femtobolt_manager: Optional[FemtoBoltManager] = None,
|
||||||
|
pressure_manager: Optional[PressureManager] = None):
|
||||||
"""
|
"""
|
||||||
初始化录制管理器
|
初始化录制管理器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
camera_manager: 相机管理器实例
|
camera_manager: 相机管理器实例
|
||||||
db_manager: 数据库管理器实例
|
db_manager: 数据库管理器实例
|
||||||
|
femtobolt_manager: FemtoBolt深度相机管理器实例
|
||||||
|
pressure_manager: 压力传感器管理器实例
|
||||||
"""
|
"""
|
||||||
self.camera_manager = camera_manager
|
self.camera_manager = camera_manager
|
||||||
self.db_manager = db_manager
|
self.db_manager = db_manager
|
||||||
|
self.femtobolt_manager = femtobolt_manager
|
||||||
|
self.pressure_manager = pressure_manager
|
||||||
|
|
||||||
# 录制状态
|
# 录制状态
|
||||||
self.sync_recording = False
|
self.sync_recording = False
|
||||||
@ -134,7 +147,7 @@ class RecordingManager:
|
|||||||
self.logger.error(f'更新数据库视频路径失败: {db_error}')
|
self.logger.error(f'更新数据库视频路径失败: {db_error}')
|
||||||
|
|
||||||
# 视频编码参数
|
# 视频编码参数
|
||||||
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
fourcc = cv2.VideoWriter_fourcc(*'avc1')
|
||||||
fps = 30
|
fps = 30
|
||||||
|
|
||||||
# 初始化足部视频写入器
|
# 初始化足部视频写入器
|
||||||
@ -152,9 +165,10 @@ class RecordingManager:
|
|||||||
self.logger.warning('相机设备未启用,跳过脚部视频写入器初始化')
|
self.logger.warning('相机设备未启用,跳过脚部视频写入器初始化')
|
||||||
|
|
||||||
# 初始化屏幕录制写入器
|
# 初始化屏幕录制写入器
|
||||||
record_size = self.screen_region[2:4] if self.screen_region else self.screen_size
|
# record_size = self.screen_region[2:4] if self.screen_region else self.screen_size
|
||||||
|
# print('屏幕写入器的宽高..............',record_size)
|
||||||
self.screen_video_writer = cv2.VideoWriter(
|
self.screen_video_writer = cv2.VideoWriter(
|
||||||
screen_video_path, fourcc, self.screen_fps, record_size
|
screen_video_path, fourcc, fps, (self.screen_size[0],self.screen_size[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.screen_video_writer.isOpened():
|
if self.screen_video_writer.isOpened():
|
||||||
@ -361,11 +375,13 @@ class RecordingManager:
|
|||||||
# 检查是否到了下一帧的时间
|
# 检查是否到了下一帧的时间
|
||||||
if current_time - last_frame_time >= frame_interval:
|
if current_time - last_frame_time >= frame_interval:
|
||||||
try:
|
try:
|
||||||
# 截取屏幕
|
# 截取屏幕self.screen_size
|
||||||
if self.screen_region:
|
if self.screen_size:
|
||||||
x, y, width, height = self.screen_region
|
# print('获取截图的时候屏幕写入器的宽高..............',self.screen_region)
|
||||||
screenshot = pyautogui.screenshot(region=(x, y, width, height))
|
width, height = self.screen_size
|
||||||
|
screenshot = pyautogui.screenshot(region=(0, 0, width, height))
|
||||||
else:
|
else:
|
||||||
|
# print('screen_region方法没找到。。。。。。。。。。。。。。。。。')
|
||||||
screenshot = pyautogui.screenshot()
|
screenshot = pyautogui.screenshot()
|
||||||
|
|
||||||
# 转换为numpy数组
|
# 转换为numpy数组
|
||||||
@ -475,6 +491,319 @@ class RecordingManager:
|
|||||||
'feet_writer_active': self.feet_video_writer is not None and self.feet_video_writer.isOpened() if self.feet_video_writer else False,
|
'feet_writer_active': self.feet_video_writer is not None and self.feet_video_writer.isOpened() if self.feet_video_writer else False,
|
||||||
'screen_writer_active': self.screen_video_writer is not None and self.screen_video_writer.isOpened() if self.screen_video_writer else False
|
'screen_writer_active': self.screen_video_writer is not None and self.screen_video_writer.isOpened() if self.screen_video_writer else False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def capture_images(self, session_id: str, patient_id: str, data_dir) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
采集屏幕截图和足部视频截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_id: 检测会话ID
|
||||||
|
patient_id: 患者ID
|
||||||
|
data_dir: 数据存储目录路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 包含截图文件路径的字典
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
'screen_image': None,
|
||||||
|
'foot_image': None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 采集屏幕截图
|
||||||
|
screen_image_path = self._capture_screen_image(data_dir)
|
||||||
|
if screen_image_path:
|
||||||
|
result['screen_image'] = str(screen_image_path)
|
||||||
|
self.logger.debug(f'屏幕截图保存成功: {screen_image_path}')
|
||||||
|
|
||||||
|
# 2. 采集足部视频截图
|
||||||
|
if self.camera_manager and self.camera_manager.is_connected:
|
||||||
|
foot_image_path = self._capture_foot_image(data_dir)
|
||||||
|
if foot_image_path:
|
||||||
|
result['foot_image'] = str(foot_image_path)
|
||||||
|
self.logger.debug(f'足部截图保存成功: {foot_image_path}')
|
||||||
|
else:
|
||||||
|
self.logger.warning('相机设备未连接,跳过足部截图')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'截图采集失败: {e}')
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def collect_detection_data(self, session_id: str, patient_id: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
采集所有设备数据并保存到指定目录结构
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session_id: 检测会话ID
|
||||||
|
patient_id: 患者ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 包含所有采集数据的字典,符合detection_data表结构
|
||||||
|
"""
|
||||||
|
# 生成采集时间戳
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3] # 精确到毫秒
|
||||||
|
|
||||||
|
# 创建数据存储目录
|
||||||
|
data_dir = Path(f'data/patients/{patient_id}/{session_id}/{timestamp}')
|
||||||
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 设置目录权限为777(完全权限)
|
||||||
|
try:
|
||||||
|
import stat
|
||||||
|
os.chmod(str(data_dir), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 777权限
|
||||||
|
self.logger.debug(f"已设置目录权限为777: {data_dir}")
|
||||||
|
except Exception as perm_error:
|
||||||
|
self.logger.warning(f"设置目录权限失败: {perm_error},但目录创建成功")
|
||||||
|
|
||||||
|
# 初始化数据字典
|
||||||
|
data = {
|
||||||
|
'session_id': session_id,
|
||||||
|
'head_pose': None,
|
||||||
|
'body_pose': None,
|
||||||
|
'body_image': None,
|
||||||
|
'foot_data': None,
|
||||||
|
'foot_image': None,
|
||||||
|
'foot_data_image': None,
|
||||||
|
'screen_image': None,
|
||||||
|
'timestamp': timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 采集头部姿态数据(从IMU设备获取)
|
||||||
|
# 注意:这里需要从外部传入IMU设备或者在初始化时添加IMU管理器
|
||||||
|
# if self.imu_manager and self.imu_manager.is_connected:
|
||||||
|
# head_pose_data = self._collect_head_pose_data()
|
||||||
|
# if head_pose_data:
|
||||||
|
# data['head_pose'] = json.dumps(head_pose_data)
|
||||||
|
# self.logger.debug(f'头部姿态数据采集成功: {session_id}')
|
||||||
|
|
||||||
|
# 2. 采集身体姿态数据(从FemtoBolt深度相机获取)
|
||||||
|
if self.femtobolt_manager and self.femtobolt_manager.is_connected:
|
||||||
|
body_pose_data = self._collect_body_pose_data()
|
||||||
|
if body_pose_data:
|
||||||
|
data['body_pose'] = json.dumps(body_pose_data)
|
||||||
|
self.logger.debug(f'身体姿态数据采集成功: {session_id}')
|
||||||
|
|
||||||
|
# 3. 采集身体视频截图(从FemtoBolt深度相机获取)
|
||||||
|
if self.femtobolt_manager and self.femtobolt_manager.is_connected:
|
||||||
|
try:
|
||||||
|
body_image_path = self._capture_body_image(data_dir)
|
||||||
|
if body_image_path:
|
||||||
|
data['body_image'] = str(body_image_path)
|
||||||
|
self.logger.debug(f'身体截图保存成功: {body_image_path}')
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'采集身体截图异常: {e}')
|
||||||
|
|
||||||
|
# 4. 采集足部压力数据(从压力传感器获取)
|
||||||
|
if self.pressure_manager and hasattr(self.pressure_manager, 'is_connected') and self.pressure_manager.is_connected:
|
||||||
|
foot_data = self._collect_foot_pressure_data()
|
||||||
|
if foot_data:
|
||||||
|
data['foot_data'] = json.dumps(foot_data)
|
||||||
|
self.logger.debug(f'足部压力数据采集成功: {session_id}')
|
||||||
|
|
||||||
|
# 5. 采集足部监测视频截图(从摄像头获取)
|
||||||
|
if self.camera_manager and self.camera_manager.is_connected:
|
||||||
|
foot_image_path = self._capture_foot_image(data_dir)
|
||||||
|
if foot_image_path:
|
||||||
|
data['foot_image'] = str(foot_image_path)
|
||||||
|
self.logger.debug(f'足部截图保存成功: {foot_image_path}')
|
||||||
|
|
||||||
|
# 6. 生成足底压力数据图(从压力传感器数据生成)
|
||||||
|
if self.pressure_manager and hasattr(self.pressure_manager, 'is_connected') and self.pressure_manager.is_connected:
|
||||||
|
foot_data_image_path = self._generate_foot_pressure_image(data_dir)
|
||||||
|
if foot_data_image_path:
|
||||||
|
data['foot_data_image'] = str(foot_data_image_path)
|
||||||
|
self.logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}')
|
||||||
|
|
||||||
|
# 7. 采集屏幕截图
|
||||||
|
screen_image_path = self._capture_screen_image(data_dir)
|
||||||
|
if screen_image_path:
|
||||||
|
data['screen_image'] = str(screen_image_path)
|
||||||
|
self.logger.debug(f'屏幕截图保存成功: {screen_image_path}')
|
||||||
|
|
||||||
|
self.logger.debug(f'数据采集完成: {session_id}, 时间戳: {timestamp}')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'数据采集失败: {e}')
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _collect_body_pose_data(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
从FemtoBolt深度相机采集身体姿态数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 身体姿态数据字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.femtobolt_manager and hasattr(self.femtobolt_manager, 'get_pose_data'):
|
||||||
|
pose_data = self.femtobolt_manager.get_pose_data()
|
||||||
|
return pose_data
|
||||||
|
else:
|
||||||
|
self.logger.warning('FemtoBolt管理器未连接或不支持姿态数据采集')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'采集身体姿态数据失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _capture_body_image(self, data_dir) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
从FemtoBolt深度相机采集身体截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dir: 数据存储目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 身体截图文件的相对路径
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.femtobolt_manager and hasattr(self.femtobolt_manager, 'get_latest_frame'):
|
||||||
|
frame = self.femtobolt_manager.get_latest_frame()
|
||||||
|
if frame is not None:
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3]
|
||||||
|
filename = f'body_{timestamp}.jpg'
|
||||||
|
file_path = data_dir / filename
|
||||||
|
|
||||||
|
# 保存图像
|
||||||
|
cv2.imwrite(str(file_path), frame)
|
||||||
|
|
||||||
|
# 返回相对路径
|
||||||
|
return str(file_path.relative_to(Path.cwd()))
|
||||||
|
else:
|
||||||
|
self.logger.warning('FemtoBolt相机未获取到有效帧')
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.logger.warning('FemtoBolt管理器未连接或不支持图像采集')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'采集身体截图失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _collect_foot_pressure_data(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
从压力传感器采集足部压力数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 足部压力数据字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.pressure_manager and hasattr(self.pressure_manager, 'get_pressure_data'):
|
||||||
|
pressure_data = self.pressure_manager.get_pressure_data()
|
||||||
|
return pressure_data
|
||||||
|
else:
|
||||||
|
self.logger.warning('压力传感器管理器未连接或不支持压力数据采集')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'采集足部压力数据失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _generate_foot_pressure_image(self, data_dir) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
生成足底压力数据图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dir: 数据存储目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 足底压力数据图文件的相对路径
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.pressure_manager and hasattr(self.pressure_manager, 'generate_pressure_heatmap'):
|
||||||
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3]
|
||||||
|
filename = f'foot_pressure_{timestamp}.jpg'
|
||||||
|
file_path = data_dir / filename
|
||||||
|
|
||||||
|
# 生成压力热力图
|
||||||
|
success = self.pressure_manager.generate_pressure_heatmap(str(file_path))
|
||||||
|
|
||||||
|
if success and file_path.exists():
|
||||||
|
# 返回相对路径
|
||||||
|
return str(file_path.relative_to(Path.cwd()))
|
||||||
|
else:
|
||||||
|
self.logger.warning('足底压力数据图生成失败')
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.logger.warning('压力传感器管理器未连接或不支持压力图生成')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'生成足底压力数据图失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _capture_screen_image(self, data_dir) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
采集屏幕截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dir: 数据存储目录路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 截图文件的相对路径,失败返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 截取屏幕
|
||||||
|
if self.screen_size:
|
||||||
|
width, height = self.screen_size
|
||||||
|
screenshot = pyautogui.screenshot(region=(0, 0, width, height))
|
||||||
|
else:
|
||||||
|
screenshot = pyautogui.screenshot()
|
||||||
|
|
||||||
|
# 保存截图
|
||||||
|
from pathlib import Path
|
||||||
|
image_path = Path(data_dir) / 'screen_image.png'
|
||||||
|
screenshot.save(str(image_path))
|
||||||
|
|
||||||
|
# 返回相对路径
|
||||||
|
abs_image_path = image_path.resolve()
|
||||||
|
abs_cwd = Path.cwd().resolve()
|
||||||
|
relative_path = abs_image_path.relative_to(abs_cwd)
|
||||||
|
|
||||||
|
return str(relative_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'屏幕截图失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _capture_foot_image(self, data_dir) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
采集足部视频截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data_dir: 数据存储目录路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 截图文件的相对路径,失败返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self.camera_manager or not self.camera_manager.is_connected:
|
||||||
|
self.logger.warning('相机设备未连接,无法采集足部截图')
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 从相机管理器获取最新帧
|
||||||
|
frame, frame_timestamp = self.camera_manager._get_latest_frame_from_cache('camera')
|
||||||
|
|
||||||
|
if frame is None:
|
||||||
|
self.logger.warning('无法从相机获取帧数据')
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 调整帧尺寸
|
||||||
|
resized_frame = cv2.resize(frame, self.MAX_FRAME_SIZE)
|
||||||
|
|
||||||
|
# 保存截图
|
||||||
|
from pathlib import Path
|
||||||
|
image_path = Path(data_dir) / 'foot_image.png'
|
||||||
|
cv2.imwrite(str(image_path), resized_frame)
|
||||||
|
|
||||||
|
# 返回相对路径
|
||||||
|
abs_image_path = image_path.resolve()
|
||||||
|
abs_cwd = Path.cwd().resolve()
|
||||||
|
relative_path = abs_image_path.relative_to(abs_cwd)
|
||||||
|
|
||||||
|
return str(relative_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'足部截图失败: {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# 保持向后兼容的ScreenRecorder类
|
# 保持向后兼容的ScreenRecorder类
|
||||||
|
@ -267,6 +267,85 @@ class AppServer:
|
|||||||
'version': '1.0.0'
|
'version': '1.0.0'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ==================== 静态文件服务 ====================
|
||||||
|
|
||||||
|
@self.app.route('/data/<path:filename>', methods=['GET'])
|
||||||
|
def serve_static_files(filename):
|
||||||
|
"""提供静态文件服务,代理backend/data/目录"""
|
||||||
|
try:
|
||||||
|
# 获取data目录的绝对路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境
|
||||||
|
data_dir = os.path.join(os.path.dirname(sys.executable), 'data')
|
||||||
|
else:
|
||||||
|
# 开发环境
|
||||||
|
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
||||||
|
|
||||||
|
# 安全检查:防止路径遍历攻击
|
||||||
|
safe_path = os.path.normpath(filename)
|
||||||
|
if '..' in safe_path or safe_path.startswith('/'):
|
||||||
|
return jsonify({'error': '非法路径'}), 400
|
||||||
|
|
||||||
|
file_path = os.path.join(data_dir, safe_path)
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return jsonify({'error': '文件不存在'}), 404
|
||||||
|
|
||||||
|
# 检查是否在允许的目录内
|
||||||
|
if not os.path.commonpath([data_dir, file_path]) == data_dir:
|
||||||
|
return jsonify({'error': '访问被拒绝'}), 403
|
||||||
|
|
||||||
|
# 返回文件
|
||||||
|
from flask import send_file
|
||||||
|
return send_file(file_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'静态文件服务错误: {e}')
|
||||||
|
return jsonify({'error': '服务器内部错误'}), 500
|
||||||
|
|
||||||
|
@self.app.route('/data/', methods=['GET'])
|
||||||
|
@self.app.route('/data', methods=['GET'])
|
||||||
|
def list_data_directory():
|
||||||
|
"""列出data目录下的文件和文件夹"""
|
||||||
|
try:
|
||||||
|
# 获取data目录的绝对路径
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包环境
|
||||||
|
data_dir = os.path.join(os.path.dirname(sys.executable), 'data')
|
||||||
|
else:
|
||||||
|
# 开发环境
|
||||||
|
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
||||||
|
|
||||||
|
if not os.path.exists(data_dir):
|
||||||
|
return jsonify({'error': 'data目录不存在'}), 404
|
||||||
|
|
||||||
|
# 获取目录内容
|
||||||
|
items = []
|
||||||
|
for item in os.listdir(data_dir):
|
||||||
|
item_path = os.path.join(data_dir, item)
|
||||||
|
is_dir = os.path.isdir(item_path)
|
||||||
|
size = os.path.getsize(item_path) if not is_dir else None
|
||||||
|
modified = datetime.fromtimestamp(os.path.getmtime(item_path)).isoformat()
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
'name': item,
|
||||||
|
'type': 'directory' if is_dir else 'file',
|
||||||
|
'size': size,
|
||||||
|
'modified': modified,
|
||||||
|
'url': f'/data/{item}' if not is_dir else None
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'path': '/data/',
|
||||||
|
'items': sorted(items, key=lambda x: (x['type'] == 'file', x['name']))
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f'目录列表错误: {e}')
|
||||||
|
return jsonify({'error': '服务器内部错误'}), 500
|
||||||
|
|
||||||
@self.app.route('/test-socketio')
|
@self.app.route('/test-socketio')
|
||||||
def test_socketio():
|
def test_socketio():
|
||||||
"""测试SocketIO连接"""
|
"""测试SocketIO连接"""
|
||||||
@ -1102,11 +1181,10 @@ class AppServer:
|
|||||||
'error': '无法获取患者ID'
|
'error': '无法获取患者ID'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
# 调用设备管理器采集数据
|
# 调用录制管理器采集数据
|
||||||
collected_data = self.device_coordinator.collect_data(
|
collected_data = self.recording_manager.collect_detection_data(
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
patient_id=patient_id,
|
patient_id=patient_id
|
||||||
screen_image_base64=screen_image_base64
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 将采集的数据保存到数据库
|
# 将采集的数据保存到数据库
|
||||||
|
Loading…
Reference in New Issue
Block a user