import os import sys import threading import time import numpy as np import pandas as pd from SunnyLinker import SunnyLinker64 from zmqServer import zmqServer from zmqClient import zmqClient from scipy.io import savemat class Parser_main(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.Running = True self.fs = 250 # 采样率 self.energy = 0 # 电量 self.status_code = 0 # 与采集设备通信的状态码,0为异常,1为正常 self.n_chan = 64 self.dataBuffer = [] self.file_num = 0#保存文件序号 self.subject_id = None #受试者ID self.session_id = None #Session ID self.last_print_time = None def connect(self): self.thread_data_server = SunnyLinker64('127.0.0.1', 7878, 250, 64, method='tcp') self.thread_data_server.toUv = True self.thread_data_server.start() self.zmqServer = zmqServer() self.zmqServer.start() self.zmqClient = zmqClient('127.0.0.1', 8088) self.zmqClient.connect() def run(self): while self.Running: # 同步信息 if self.zmqServer.state_mode == 'sync': self.zmqClient.send_to_all('sync', self.zmqClient.state) self.zmqServer.state_mode = 'rest' # 状态异常,报告上位机 if self.status_code != self.thread_data_server.status_code: self.status_code = self.thread_data_server.status_code self.zmqClient.send_to_all('status_code', int(self.status_code)) # 返回电量 if self.energy != self.thread_data_server.energy: self.energy = self.thread_data_server.energy self.zmqClient.send_to_all('energy', int(self.energy)) # 更新文件序号 if self.subject_id != self.zmqServer.subject_id or self.session_id != self.zmqServer.session_id: self.subject_id = self.zmqServer.subject_id self.session_id = self.zmqServer.session_id self.file_num = 0 #从零开始计数 if self.zmqServer.open_Impedance == True: # 开启阻抗检测功能,仅运行一次 self.thread_data_server.Impedance(True) self.zmqServer.open_Impedance = -1 elif self.zmqServer.open_Impedance == False: self.thread_data_server.Impedance(False) self.zmqServer.open_Impedance = -1 if self.zmqServer.get_Impedance: # 返回阻抗值 if self.thread_data_server.GetDataLenCount() > self.fs: Impe_data = self.thread_data_server.getData(self.fs) # 计算阻抗 imps = self.thread_data_server.getImpedance(Impe_data, self.n_chan) self.zmqClient.send_to_all('impedance', imps.tolist()) else: pass if self.thread_data_server.GetDataLenCount() < 50: time.sleep(0.01) continue if self.zmqServer.get_Impedance == False: # 非阻抗检测状态 data = self.thread_data_server.getData(50) data = data[:self.n_chan, :] if self.zmqServer.mat_generate: # 检测是否需要重置缓冲区(第二次发送 matGenerate 时清空旧数据) if self.zmqServer.reset_mat_buffer: self.dataBuffer = [] self.last_print_time = None self.zmqServer.reset_mat_buffer = False print('[INFO] 数据缓冲区已重置,从头开始采集') self.dataBuffer.append(data) if len(self.dataBuffer) % 50 == 0: current_time = time.time() if self.last_print_time is not None: elapsed_time = current_time - self.last_print_time # 2500个点 = 50个数据块 * 50个采样点/数据块 actual_fs = 2500 / elapsed_time print(f"接收 2500 个采样点耗时: {elapsed_time:.4f} 秒, 折合实际采样率: {actual_fs:.2f} Hz") else: print("开始计时...") self.last_print_time = current_time print('数据保存进度: {}/{}'.format(len(self.dataBuffer),int(self.zmqServer.save_win*self.fs//50))) if len(self.dataBuffer) >= int(self.zmqServer.save_win*self.fs//50): #5分钟*60秒*250Hz / 50 self.zmqServer.mat_generate = False matData = np.hstack(self.dataBuffer[:int(self.zmqServer.save_win*self.fs//50)]) self.dataBuffer = [] self.last_print_time = None # 重置计时器以备下次使用 self.pack2mat(matData,self.subject_id,self.session_id) def pack2mat(self,data,subject_id,session_id): #EEG数据 Data = data.T #通道名称 channel_names = np.array( ['AIN1', 'AIN2', 'AIN3', 'AIN4', 'AIN5', 'AIN6', 'AIN7', 'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14', 'AIN15', 'AIN16', 'AIN17', 'AIN18', 'AIN19', 'AIN20', 'AIN21', 'AIN22', 'AIN23', 'AIN24', 'AIN25', 'AIN26', 'AIN27', 'AIN28', 'AIN29', 'AIN30', 'AIN31', 'AIN32', 'AIN33', 'AIN34', 'AIN35', 'AIN36', 'AIN37', 'AIN38', 'AIN39', 'AIN40', 'AIN41', 'AIN42', 'AIN43', 'AIN44', 'AIN45', 'AIN46', 'AIN47', 'AIN48', 'AIN49', 'AIN50', 'AIN51', 'AIN52', 'AIN53', 'AIN54', 'AIN55', 'AIN56', 'AIN57', 'AIN58', 'AIN59', 'AIN60', 'AIN61', 'AIN62', 'AIN63', 'AIN64'], dtype=object) #采样率 sample_rate = self.fs #通道数量 node_number = Data.shape[1] # 时间轴 t = np.linspace(0, self.zmqServer.save_win, Data.shape[0]) t = t.reshape(len(t), 1) #电极名称 electrode_name = np.array(['FP1', 'FP2', 'PO6', 'POZ', 'F3', 'F4', 'FPZ', 'AF4', 'FC3', 'PO8', 'CP2', 'CP1', 'FCZ', 'PO5', 'FC2', 'FC1', 'C3', 'C4', 'FC4', 'CP4', 'P3', 'P4', 'F5', 'C5', 'F6', 'PO4', 'CP6', 'CP5', 'PO3', 'CP3', 'FC6', 'FC5', 'CB1', 'CB2', 'P5', 'AF7', 'A1','T7', 'FT7', 'TP7', 'FT8', 'AF8', 'F8', 'F7', 'P6', 'C6', 'O2', 'O1', 'T8', 'P7', 'CZ','PZ', 'P8', 'FZ', 'OZ', 'PO7', 'TP8', 'AF3', 'C2', 'C1', 'P2', 'P1', 'F2', 'F1'], dtype=object) #电极三维坐标 electrode_xyz = self.read_ch_pos() electrode_xyz.update({'A1': [-0.095, 0, -0.005]}) electrode_xyz = {key: electrode_xyz[key] for key in electrode_name} electrode_xyz = np.array(list(electrode_xyz.values())) #电极坐标所属的坐标系 electrode_coord_system = '10-20 spherical model' #受试者ID Subject_id = subject_id #Session ID Session_id = session_id #参考电极方案 ref = 'CPZ' #数据采集开始时间 start_time = 0 meta_struct = { 'subject_id': Subject_id, 'session_id': Session_id, 'ref': ref, 'start_time': start_time } eeg_struct = { 'data': Data, 'chn': channel_names, 'sample_rate': sample_rate, 'node_number': node_number, 't': t, 'electrode_name': electrode_name, 'electrode_xyz': electrode_xyz, 'electrode_coord_system': electrode_coord_system, 'meta': meta_struct, } fileDir = os.path.join('EEGfiles/',Subject_id,Session_id) os.makedirs(fileDir,exist_ok=True) filePath = os.path.join(fileDir,'eeg_data{}.mat'.format(self.file_num)) # 保存到 .mat 文件,顶层变量名为 'eeg' savemat(filePath, {'eeg': eeg_struct}) print('EEGfile saved at {}'.format(filePath)) self.zmqClient.send_to_all('filePath', filePath) self.file_num += 1 def read_ch_pos(self,file_path=r'xy_64.xlsx'): """ 将电极位置信息转换为Dict 参数: file_path: 电极位置存储文件, 必须包含'channel', 'x', 'y', 'z'列 """ if getattr(sys, 'frozen', False): script_dir = sys._MEIPASS else: script_dir = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(script_dir, file_path) df = pd.read_excel(file_path) # 确保列名正确 if not all(col in df.columns for col in ['channel', 'x', 'y', 'z']): raise ValueError("DataFrame必须包含'channel', 'x', 'y', 'z'列") # 创建电极位置字典 ch_pos = {} for _, row in df.iterrows(): ch_pos[row['channel']] = [row['x'], row['y'], row['z']] return ch_pos def stop(self): ''' 停止运行 @return: ''' self.zmqServer.stop() self.Running=False