From 7bfe1f5acb639f78ca9c9ecacfaea0fc6ebdf57a Mon Sep 17 00:00:00 2001 From: zhaozilong12 <405241463@qq.com> Date: Wed, 6 Aug 2025 08:48:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=95=E5=83=8F=E6=88=AA=E5=9B=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.py | 97 ++++++++++-------- backend/data/body_balance.db | Bin 114688 -> 122880 bytes backend/database.py | 20 ++-- backend/device_manager.py | 187 ++++++++++++++++++++++------------- 4 files changed, 182 insertions(+), 122 deletions(-) diff --git a/backend/app.py b/backend/app.py index 157a24d5..e721813b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -618,47 +618,28 @@ def stop_video_streaming(): def start_detection(): """开始检测""" try: - if not db_manager: - return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500 + if not db_manager or not device_manager: + return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500 data = flask_request.get_json() - patient_id = data.get('patientId') or data.get('patient_id') - settings = data.get('settings', '{}') + patient_id = data.get('patient_id') creator_id = data.get('creator_id') + if not patient_id or not creator_id: + return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400 - if not patient_id: - return jsonify({ - 'success': False, - 'error': '缺少必要参数: patient_id' - }), 400 + # 调用create_detection_session方法,settings传空字典 + session_id = db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id) - # 解析设置参数 - if isinstance(settings, str): - try: - settings = json.loads(settings) - except json.JSONDecodeError: - settings = {} + # 开始同步录制 + recording_response = None + try: + recording_response = device_manager.start_recording(session_id, patient_id) + except Exception as rec_e: + logger.error(f'开始同步录制失败: {rec_e}') - # 创建检测会话 - session_id = db_manager.create_detection_session( - patient_id=str(patient_id), - settings=settings, - creator_id=creator_id - ) + start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - if session_id: - logger.info(f'检测会话已创建 - 会话ID: {session_id}, 患者ID: {patient_id}') - return jsonify({ - 'success': True, - 'session_id': session_id, - 'message': '检测会话创建成功' - }) - else: - return jsonify({ - 'success': False, - 'error': '创建检测会话失败' - }), 500 - + return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response}) except Exception as e: logger.error(f'开始检测失败: {e}') return jsonify({'success': False, 'error': str(e)}), 500 @@ -667,15 +648,50 @@ def start_detection(): def stop_detection(session_id): """停止检测""" try: - if not db_manager: - return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500 + if not db_manager or not device_manager: + logger.error('数据库管理器或设备管理器未初始化') + return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500 if not session_id: + logger.error('缺少会话ID') return jsonify({ 'success': False, 'error': '缺少会话ID' }), 400 + data = flask_request.get_json() + logger.debug(f'接收到停止检测请求,session_id: {session_id}, 请求数据: {data}') + # video_data = data.get('videoData') if data else None + video_data = data['videoData'] + mime_type = data.get('mimeType', 'video/webm;codecs=vp9') # 默认webm格式 + + # 验证base64视频数据格式 + if not video_data.startswith('data:video/'): + return jsonify({ + 'success': False, + 'message': '无效的视频数据格式' + }), 400 + # try: + # header, encoded = video_data.split(',', 1) + # video_bytes = base64.b64decode(encoded) + # except Exception as e: + # return jsonify({ + # 'success': False, + # 'message': f'视频数据解码失败: {str(e)}' + # }), 400 + # 停止同步录制,传递视频数据 + try: + logger.debug(f'调用device_manager.stop_recording,session_id: {session_id}, video_data长度: {len(video_data) if video_data else 0}') + if video_data is None: + logger.warning(f'视频数据为空,session_id: {session_id}') + else: + logger.debug(f'视频数据长度: {len(video_data)} 字符,约 {len(video_data)*3/4/1024:.2f} KB, session_id: {session_id}') + restrt=device_manager.stop_recording(session_id, video_data_base64=video_data) + logger.error(restrt) + except Exception as rec_e: + logger.error(f'停止同步录制失败: {rec_e}', exc_info=True) + raise + # 更新会话状态为已完成 success = db_manager.update_session_status(session_id, 'completed') @@ -686,13 +702,14 @@ def stop_detection(session_id): 'message': '检测已停止' }) else: + logger.error('停止检测失败,更新会话状态失败') return jsonify({ 'success': False, 'error': '停止检测失败' }), 500 except Exception as e: - logger.error(f'停止检测失败: {e}') + logger.error(f'停止检测失败: {e}', exc_info=True) return jsonify({'success': False, 'error': str(e)}), 500 @app.route('/api/detection//status', methods=['GET']) @@ -823,6 +840,7 @@ def stop_sync_recording(): data = flask_request.get_json() session_id = data.get('session_id') + video_data = data.get('videoData') # 新增接收前端传递的视频数据 if not session_id: return jsonify({ @@ -830,20 +848,19 @@ def stop_sync_recording(): 'error': '缺少必要参数: session_id' }), 400 - result = device_manager.stop_recording(session_id) + result = device_manager.stop_recording(session_id, video_data_base64=video_data) if result['success']: logger.info(f'同步录制已停止 - 会话ID: {session_id}') return jsonify(result) else: return jsonify(result), 500 - + except Exception as e: logger.error(f'停止同步录制失败: {e}') return jsonify({'success': False, 'error': str(e)}), 500 - @app.route('/api/history/sessions', methods=['GET']) def get_detection_sessions(): """获取检测会话历史""" diff --git a/backend/data/body_balance.db b/backend/data/body_balance.db index 957f42e9e42cfa676adcef78ddd50dc5bc6b54e0..278245b95ab976507452412d8b1b62832036919a 100644 GIT binary patch literal 122880 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

5>w-Jl8UvfxxiiJnq#Y2aB~aTS_Lk{k z#x%2{yQQ@;aQjBLi`mWYA57)scw#iC`iW-`WOMRRJS*pNsdRrf&YJ}Ohria+6xg`Y zeP}3`xMxr{?KW-t3aRVedh^b%plN4tLyI~kz47-x`9QF@>(1WbZM%1LZ{58w_?51G ze{4L&ytQdlaB=4Cg)1{l&%d>Fd`3C;*e@?nDaU8$-yF|BcX)C3&@V4PV(IJt#Neo` zdh6NM8|>M;b7vJ-!BSt!_+?A8Njcj$k{YJ|Fljj~E`4ev}otP^#9{Pf}F|aFS zmpSzE$?9+3xZOsd|F89TxX7=_&&fZME99@pDe@>eKnBQH$t^@8pC>Cxo&VSVU-*CG zf7^f7f7*Y#M8u)s_0{3Vn6CzPe0bCHksg zU-jv$ZTf1fzS^R%diB+2eYHtnZPZsA^woNO)uXT0(eeLOI_&=6cfduK$WO^z4jvyC_2~tcD*ILNMn8aOgA?IQo*KUwY!7vx& zg&5CWS1#wmF(Dc-$@yqZio`^IO}Sj`jPd+x3pp1HcdROx3(=SmUTGm0Vp8Xda(P&a z@zLcLay}N0F0+tByb>vwLkL2L-%`$peHQX?j2GK1}xgbYcXtj4O`6yy(Ys}qv6N~!@hdMmd9{pU7P0yZ!vEA{NGFNaFKV(zmgx4 z*U1ZHnmkBSAi@O+AOR$R1dsp{Kmter2_OL^fCP{L5~xUEg(swqkI=zI1W6FRCXpy{ z&E=w|5|PA5d0ylj4Wft`hIax?A`V^$Fp7jucoV=R65u6(<(`oC^}obN!cq14zn6q8 z-v6%{Anrs0NB{{S0VIF~kN^@u0!RP}AOR%s=_FuyuU`9JL*$L`|7&ut!}$KcDu?el zq_X$_qwtXhd|zPw{=dXozyB|Z*6;uGVVn2=BNp%f3-CQfls^BjBk#D#2k?Un5 zENlrJ$MNt#{8EWF(;`eyNoT+`i9{HDq4gUNB@?;CUBii7N>1mpca<8_HVQuOx>uHS zHw_JowQJmyNgi;fk?k9iAJBM<7s z84sr6hzHZC%7aBRx=h0X1;ms+!hER=A@-KZ1N3HH73Sd zD=>#4XRG?96Xn*-91J>SPBok{ryA9n(_~NjS~Tco87?A4#YpAg3$pt4HA}edl zgUp8`9;${D9;$}U!!k1zA{q5+ILhj-hy)Ewbt-IK6jvH;R;FcGw zRGDA|&ZVKs1W{P6&g`P48WUqpwpl+S@zrWH=2z&bG3zsf4o@{C!BN&%edwp!vbrfG zdt$87K6p_StJJkHOE{gVy4K*SL1)1;4M+K$X;clKC417>pyjyoa!&`iC|;EZorS#S%m4-*22VAd1y42f z;B88RNRvG==CKc+j-6gDcsA8~t-(`+&Vpwej)G?zRfA{Ap7htLC84hTZYCnYDDhQ; zr}N1hOM=i53-hikH5LWx#32x>;S31X(1F+#1-LQe!xLj}``kds$gc(@9EE}XYRwJQ zptInahNIw_h9104QDDiY_y69ee}nsf|54wAZG)|Mwe0kMvH43N!UYK+0VIF~kN^_6 z775(FR=co))I-N)v4^lQDN%(B8*LqN{4;L47#m!89E4U>a3exft03cR{)rR#*|<=+n;KXY Vm~hJ;U`fC&i)Iu_++_`n4glyYDc=A9 diff --git a/backend/database.py b/backend/database.py index 8a1eae6b..89170f6e 100644 --- a/backend/database.py +++ b/backend/database.py @@ -632,21 +632,19 @@ class DatabaseManager: cursor.execute(''' INSERT INTO detection_sessions ( - id, patient_id, creator_id, duration, frequency, settings, status, - diagnosis_info, treatment_info, suggestion_info, notes, start_time, created_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + id, patient_id, creator_id, duration, settings, status, + diagnosis_info, treatment_info, suggestion_info, start_time, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( session_id, patient_id, creator_id, settings.get('duration', 60), - settings.get('frequency', 60), json.dumps(settings), - 'created', + 'running', settings.get('diagnosis_info', ''), settings.get('treatment_info', ''), settings.get('suggestion_info', ''), - settings.get('notes', ''), china_time, china_time )) @@ -660,7 +658,7 @@ class DatabaseManager: logger.error(f'创建检测会话失败: {e}') raise - def update_session_status(self, session_id: str, status: str, data_points: int = 0): + def update_session_status(self, session_id: str, status: str): """更新会话状态""" conn = self.get_connection() cursor = conn.cursor() @@ -671,15 +669,15 @@ class DatabaseManager: china_time = self.get_china_time() cursor.execute(''' UPDATE detection_sessions SET - status = ?, data_points = ?, end_time = ? + status = ?, end_time = ? WHERE id = ? - ''', (status, data_points, china_time, session_id)) + ''', (status, china_time, session_id)) else: cursor.execute(''' UPDATE detection_sessions SET - status = ?, data_points = ? + status = ? WHERE id = ? - ''', (status, data_points, session_id)) + ''', (status, session_id)) conn.commit() logger.info(f'更新会话状态: {session_id} -> {status}') diff --git a/backend/device_manager.py b/backend/device_manager.py index 8023f7aa..7f10b1c6 100644 --- a/backend/device_manager.py +++ b/backend/device_manager.py @@ -24,6 +24,7 @@ from concurrent.futures import ThreadPoolExecutor import logging # 数据库管理 +# from backend.app import get_detection_sessions from database import DatabaseManager # FemtoBolt深度相机支持 @@ -265,10 +266,14 @@ class DeviceManager: if platform.system() == "Windows": # 优先使用Orbbec SDK K4A Wrapper(与azure_kinect_image_example.py一致) - orbbec_paths = [ - r"D:\OrbbecSDK_K4A_Wrapper_v1.10.3_windows_202408091749\bin\k4a.dll", - ] - + base_dir = os.path.dirname(os.path.abspath(__file__)) + dll_path = os.path.join(base_dir, "dll", "bin", "k4a.dll") + orbbec_paths = [] + if os.path.exists(dll_path): + orbbec_paths.append(dll_path) + # orbbec_paths = [ + # r"D:\BodyBalanceEvaluation\backend\dll\bin\k4a.dll", + # ] # Azure Kinect SDK标准安装路径(备用) standard_paths = [ r"C:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\bin\k4a.dll", @@ -498,54 +503,80 @@ class DeviceManager: } try: - # 1. 采集头部姿态数据(从IMU设备获取) - if self.device_status['imu']: - head_pose_data = self._collect_head_pose_data() - if head_pose_data: - data['head_pose'] = json.dumps(head_pose_data) - logger.debug(f'头部姿态数据采集成功: {session_id}') + # # 1. 采集头部姿态数据(从IMU设备获取) + # if self.device_status['imu']: + # head_pose_data = self._collect_head_pose_data() + # if head_pose_data: + # data['head_pose'] = json.dumps(head_pose_data) + # logger.debug(f'头部姿态数据采集成功: {session_id}') - # 2. 采集身体姿态数据(从FemtoBolt深度相机获取) - if self.device_status['femtobolt']: - body_pose_data = self._collect_body_pose_data() - if body_pose_data: - data['body_pose'] = json.dumps(body_pose_data) - logger.debug(f'身体姿态数据采集成功: {session_id}') + # # 2. 采集身体姿态数据(从FemtoBolt深度相机获取) + # if self.device_status['femtobolt']: + # body_pose_data = self._collect_body_pose_data() + # if body_pose_data: + # data['body_pose'] = json.dumps(body_pose_data) + # logger.debug(f'身体姿态数据采集成功: {session_id}') - # 3. 采集身体视频截图(从FemtoBolt深度相机获取) - if self.device_status['femtobolt']: - body_image_path = self._capture_body_image(data_dir) - if body_image_path: - data['body_image'] = str(body_image_path) - logger.debug(f'身体截图保存成功: {body_image_path}') + # # 3. 采集身体视频截图(从FemtoBolt深度相机获取) + # if self.device_status['femtobolt']: + # body_image_path = self._capture_body_image(data_dir) + # if body_image_path: + # data['body_image'] = str(body_image_path) + # logger.debug(f'身体截图保存成功: {body_image_path}') - # 4. 采集足部压力数据(从压力传感器获取) - if self.device_status['pressure']: - foot_data = self._collect_foot_pressure_data() - if foot_data: - data['foot_data'] = json.dumps(foot_data) - logger.debug(f'足部压力数据采集成功: {session_id}') + # # 4. 采集足部压力数据(从压力传感器获取) + # if self.device_status['pressure']: + # foot_data = self._collect_foot_pressure_data() + # if foot_data: + # data['foot_data'] = json.dumps(foot_data) + # logger.debug(f'足部压力数据采集成功: {session_id}') - # 5. 采集足部监测视频截图(从摄像头获取) - if self.device_status['camera']: - foot_image_path = self._capture_foot_image(data_dir) - if foot_image_path: - data['foot_image'] = str(foot_image_path) - logger.debug(f'足部截图保存成功: {foot_image_path}') + # # 5. 采集足部监测视频截图(从摄像头获取) + # if self.device_status['camera']: + # foot_image_path = self._capture_foot_image(data_dir) + # if foot_image_path: + # data['foot_image'] = str(foot_image_path) + # logger.debug(f'足部截图保存成功: {foot_image_path}') - # 6. 生成足底压力数据图(从压力传感器数据生成) - if self.device_status['pressure']: - 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) - logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}') + # # 6. 生成足底压力数据图(从压力传感器数据生成) + # if self.device_status['pressure']: + # 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) + # logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}') # 7. 保存屏幕录制截图(从前端传入的base64数据) if screen_image_base64: - screen_image_path = self._save_screen_image(data_dir, screen_image_base64) - if screen_image_path: - data['screen_image'] = str(screen_image_path) - logger.debug(f'屏幕截图保存成功: {screen_image_path}') + try: + logger.debug(f'屏幕截图保存.................{screen_image_base64}') + # 保存屏幕截图的base64数据为图片文件 + screen_image_path = None + if screen_image_base64: + try: + if screen_image_base64.startswith('data:image/'): + base64_data = screen_image_base64.split(',')[1] + else: + base64_data = screen_image_base64 + image_data = base64.b64decode(base64_data) + image_path = data_dir / 'screen_image.png' + with open(image_path, 'wb') as f: + f.write(image_data) + abs_image_path = image_path.resolve() + abs_cwd = Path.cwd().resolve() + screen_image_path = str(abs_image_path.relative_to(abs_cwd)) + logger.debug(f'屏幕截图保存成功: {screen_image_path}') + except Exception as e: + logger.error(f'屏幕截图保存失败: {e}') + import traceback + logger.error(traceback.format_exc()) + + if screen_image_path: + data['screen_image'] = str(screen_image_path) + logger.debug(f'屏幕截图保存成功: {screen_image_path}') + except Exception as e: + logger.error(f'屏幕截图保存失败: {e}') + import traceback + logger.error(traceback.format_exc()) # 更新最新数据 with self.data_lock: @@ -1073,10 +1104,10 @@ class DeviceManager: body_video_path, fourcc, fps, (1280, 720) ) - # 屏幕录制写入器(默认分辨率,后续根据实际帧调整) - self.screen_video_writer = cv2.VideoWriter( - screen_video_path, fourcc, fps, (1920, 1080) - ) + # # 屏幕录制写入器(默认分辨率,后续根据实际帧调整) + # self.screen_video_writer = cv2.VideoWriter( + # screen_video_path, fourcc, fps, (1920, 1080) + # ) # 重置停止事件 self.recording_stop_event.clear() @@ -1097,14 +1128,14 @@ class DeviceManager: name='BodyRecordingThread' ) self.body_recording_thread.start() - #屏幕录制 - if self.screen_video_writer: - self.screen_recording_thread = threading.Thread( - target=self._screen_recording_thread, - daemon=True, - name='ScreenRecordingThread' - ) - self.screen_recording_thread.start() + # #屏幕录制 + # if self.screen_video_writer: + # self.screen_recording_thread = threading.Thread( + # target=self._screen_recording_thread, + # daemon=True, + # name='ScreenRecordingThread' + # ) + # self.screen_recording_thread.start() # 设置录制状态 self.sync_recording = True @@ -1122,21 +1153,15 @@ class DeviceManager: return result - def stop_recording(self, session_id: str) -> Dict[str, Any]: + def stop_recording(self, session_id: str, video_data_base64: str = None) -> Dict[str, Any]: """停止同步录制 Args: session_id: 检测会话ID - + video_data_base64: 屏幕录制视频的base64编码数据,可选 + Returns: Dict: 录制停止状态和信息 - { - 'success': bool, - 'session_id': str, - 'recording_duration': float, - 'video_files': List[str], - 'message': str - } """ result = { 'success': False, @@ -1158,12 +1183,18 @@ class DeviceManager: # 设置停止事件 self.recording_stop_event.set() + session_data = self.db_manager.get_session_data(session_id) + base_path = os.path.join('data', 'patients', session_data['patient_id'], session_id) + + # 定义视频文件路径 + feet_video_path = os.path.join(base_path, 'feet.mp4') + body_video_path = os.path.join(base_path, 'body.mp4') + screen_video_path = os.path.join(base_path, 'screen.mp4') # 等待录制线程结束 threads_to_join = [ (self.feet_recording_thread, 'feet'), - (self.body_recording_thread, 'body'), - (self.screen_recording_thread, 'screen') + (self.body_recording_thread, 'body') ] for thread, name in threads_to_join: @@ -1179,16 +1210,30 @@ class DeviceManager: # 清理视频写入器并收集文件信息 video_files = self._cleanup_video_writers() + + # 保存传入的屏幕录制视频数据,替代原有屏幕录制视频保存逻辑 + if video_data_base64: + try: + video_bytes = base64.b64decode(video_data_base64) + with open(screen_video_path, 'wb') as f: + f.write(video_bytes) + video_files.append(screen_video_path) + logger.info(f'屏幕录制视频保存成功,路径: {screen_video_path}, 文件大小: {os.path.getsize(screen_video_path)} 字节') + except Exception as e: + logger.error(f'保存屏幕录制视频失败: {e}', exc_info=True) + logger.debug(f'视频数据长度: {len(video_data_base64)}') + raise + result['video_files'] = video_files # 更新数据库中的会话信息 if self.db_manager and result['recording_duration'] > 0: try: - # 更新会话持续时间 duration_seconds = int(result['recording_duration']) self.db_manager.update_session_duration(session_id, duration_seconds) - - # 更新会话状态为已完成 + self.db_manager.update_session_normal_video_path(session_id, feet_video_path) + self.db_manager.update_session_femtobolt_video_path(session_id, body_video_path) + self.db_manager.update_session_screen_video_path(session_id, screen_video_path) self.db_manager.update_session_status(session_id, 'completed') logger.debug(f'数据库会话信息更新成功 - 会话ID: {session_id}, 持续时间: {duration_seconds}秒') @@ -1207,7 +1252,7 @@ class DeviceManager: logger.debug(f'同步录制已停止 - 会话ID: {session_id}, 录制时长: {result["recording_duration"]:.2f}秒') except Exception as e: - logger.error(f'停止同步录制失败: {e}') + logger.error(f'停止同步录制失败: {e}', exc_info=True) result['message'] = f'停止录制失败: {str(e)}' return result