commit 97c6be4e8dead31e1e9ba57dcbe972c4b2832897 Author: SimonovaMI Date: Tue Apr 29 16:31:44 2025 +0300 First commit diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f42f8c0 --- /dev/null +++ b/README.txt @@ -0,0 +1,5 @@ +Для ознакомления с кодом проще всего запустить программу в PyCharm +1. создать папку с проектом +2. в папке с проектом создать папку project +3. в папку project поместить файлы с кодом +4. в папку с проектом разместить файл ui и ico diff --git a/design_viewer.ui b/design_viewer.ui new file mode 100644 index 0000000..f655a5d --- /dev/null +++ b/design_viewer.ui @@ -0,0 +1,2423 @@ + + + MainWindow + + + + 0 + 0 + 1920 + 1000 + + + + MainWindow + + + Qt::LeftToRight + + + + + + 200 + 30 + 454 + 41 + + + + + + + true + + + Открыть файл + + + + + + + Закрыть файл + + + + + + + + true + + + + 1410 + 80 + 501 + 251 + + + + QFrame::WinPanel + + + QFrame::Raised + + + 3 + + + + + 30 + 10 + 255 + 16 + + + + + 75 + true + + + + Настройки детекции событий + + + Qt::AlignCenter + + + + + + 13 + 68 + 261 + 22 + + + + QFrame::Box + + + Минимальная чувствительность, мВ + + + Qt::AlignCenter + + + + + + 14 + 127 + 261 + 22 + + + + QFrame::Box + + + Длительность защиты от Т волны, мс + + + Qt::AlignCenter + + + + + + 14 + 156 + 261 + 22 + + + + QFrame::Box + + + Порог чувствительности Т волны + + + Qt::AlignCenter + + + + + + 273 + 68 + 61 + 22 + + + + true + + + 4 + + + + 4.0 + + + + + 3.5 + + + + + 3.0 + + + + + 2.5 + + + + + 2.0 + + + + + 1.5 + + + + + 1.25 + + + + + 1.0 + + + + + 0.75 + + + + + 0.5 + + + + + + + 14 + 97 + 261 + 22 + + + + QFrame::Box + + + Максимальная чувствительность, мВ + + + Qt::AlignCenter + + + + + + 273 + 97 + 61 + 22 + + + + true + + + 3 + + + + 15.0 + + + + + 12.0 + + + + + 10.0 + + + + + 8.0 + + + + + 6.0 + + + + + 4.0 + + + + + 3.5 + + + + + 3.0 + + + + + 2.5 + + + + + + + 274 + 127 + 61 + 22 + + + + true + + + 2 + + + + 300 + + + + + 325 + + + + + 350 + + + + + 375 + + + + + 400 + + + + + + + 274 + 156 + 61 + 22 + + + + true + + + 0 + + + + 0.75 + + + + + 0.55 + + + + + 0.35 + + + + + 0.25 + + + + + + + 13 + 39 + 261 + 22 + + + + + 8 + + + + QFrame::Box + + + Длительность поиска максимума, мс + + + Qt::AlignCenter + + + + + + 273 + 39 + 61 + 22 + + + + + 8 + + + + true + + + 3 + + + + 120 + + + + + 125 + + + + + 130 + + + + + 135 + + + + + 140 + + + + + 145 + + + + + 150 + + + + + + + 274 + 185 + 61 + 22 + + + + + 0 + 2 + + + + true + + + 3 + + + + 500 + + + + + 750 + + + + + 1000 + + + + + 1250 + + + + + 1500 + + + + + 1750 + + + + + 2000 + + + + + + + 14 + 185 + 261 + 22 + + + + QFrame::Box + + + Максимальный размер RR интервала, мс + + + Qt::AlignCenter + + + + + + 274 + 218 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 14 + 218 + 261 + 22 + + + + QFrame::Box + + + Минимально регистрируемая ЧСС, Гц + + + Qt::AlignCenter + + + + + + 340 + 30 + 141 + 22 + + + + Размер окна, сек + + + Qt::AlignCenter + + + + + + 380 + 50 + 61 + 22 + + + + true + + + 2 + + + + 30 + + + + + 20 + + + + + 10 + + + + + 8 + + + + + 6 + + + + + 4 + + + + + 2 + + + + + 1 + + + + + + + 370 + 100 + 91 + 20 + + + + с фильтром + + + 1 + + + + без фильтра + + + + + с фильтром + + + + + + + 380 + 80 + 81 + 20 + + + + Вывод сигнала + + + + + + 350 + 120 + 141 + 20 + + + + Активный канал передачи + + + + + + 370 + 140 + 91 + 20 + + + + 2 + + + 2 + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + 350 + 160 + 141 + 20 + + + + Постоянная составляющая + + + + + + 370 + 180 + 91 + 20 + + + + без + + + 1 + + + + с + + + + + без + + + + + + + + 20 + 80 + 1371 + 111 + + + + + + + 20 + 190 + 1371 + 601 + + + + + + + 170 + 0 + 321 + 21 + + + + QFrame::Box + + + 0 + + + + + + Qt::AlignCenter + + + + + + 680 + 30 + 641 + 41 + + + + + + + + + + + + + + true + + + Старт + + + + + + + true + + + Стоп + + + + + + + true + + + Начать запись + + + + + + + true + + + Закончить запись + + + + + + + + + 780 + 0 + 201 + 21 + + + + QFrame::Box + + + 0 + + + Вывод сигнала по COM-порту + + + Qt::AlignCenter + + + + + true + + + + 1410 + 340 + 501 + 611 + + + + QFrame::WinPanel + + + QFrame::Raised + + + 3 + + + + + 170 + 30 + 201 + 22 + + + + Верхние пороги корзин, мс + + + Qt::AlignCenter + + + + + + 20 + 70 + 71 + 21 + + + + 250 + + + 350 + + + 5 + + + 250 + + + + + + 230 + 70 + 81 + 21 + + + + 300 + + + 400 + + + 5 + + + 400 + + + + + + 390 + 70 + 81 + 21 + + + + 350 + + + 450 + + + 5 + + + 450 + + + + + + 20 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 0 + 100 + 171 + 22 + + + + Порог корзины фибриляций + + + Qt::AlignCenter + + + + + + 20 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 220 + 150 + 91 + 16 + + + + + 75 + true + + + + Корзины + + + Qt::AlignCenter + + + + + + 10 + 170 + 81 + 20 + + + + Фибрилляция + + + Qt::AlignCenter + + + + + + 140 + 170 + 71 + 20 + + + + ЖТ2 + + + Qt::AlignCenter + + + + + + 270 + 170 + 61 + 20 + + + + ЖТ1 + + + Qt::AlignCenter + + + + + + 390 + 170 + 71 + 21 + + + + Норма + + + Qt::AlignCenter + + + + + + 160 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 280 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 410 + 190 + 71 + 16 + + + + 5 + + + 0 + + + %p + + + + + + 140 + 10 + 255 + 16 + + + + + 75 + true + + + + Настройки корзин + + + Qt::AlignCenter + + + + + + 10 + 50 + 91 + 22 + + + + Фибриляция + + + Qt::AlignCenter + + + + + + 210 + 50 + 91 + 22 + + + + Тахикардия 2 + + + Qt::AlignCenter + + + + + + 370 + 50 + 91 + 22 + + + + Тахикардия 1 + + + Qt::AlignCenter + + + + + + 210 + 100 + 131 + 22 + + + + Порог корзины ЖТ2 + + + Qt::AlignCenter + + + + + + 370 + 100 + 121 + 22 + + + + Порог корзины ЖТ1 + + + Qt::AlignCenter + + + + + + 230 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 390 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 20 + 260 + 191 + 22 + + + + Кол-во ступеней ВВ терапии + + + Qt::AlignCenter + + + + + + 240 + 260 + 41 + 21 + + + + 1 + + + 8 + + + 8 + + + + + + 19 + 288 + 191 + 22 + + + + Текущая ступень ВВ терапии + + + Qt::AlignCenter + + + + + + 240 + 288 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 20 + 316 + 191 + 22 + + + + Минимальная энергия, Дж + + + Qt::AlignCenter + + + + + + 240 + 317 + 81 + 21 + + + + 2 + + + 19 + + + 5 + + + 10 + + + + + + 20 + 344 + 191 + 22 + + + + Максимальная энергия, Дж + + + Qt::AlignCenter + + + + + + 240 + 345 + 81 + 21 + + + + 20 + + + 40 + + + 40 + + + + + + 20 + 374 + 191 + 22 + + + + Текущая энергия, Дж + + + Qt::AlignCenter + + + + + + 241 + 373 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 20 + 400 + 191 + 22 + + + + Полярность + + + Qt::AlignCenter + + + + + + 170 + 400 + 151 + 22 + + + + + 0 + 2 + + + + true + + + Положительная + + + 0 + + + + Положительная + + + + + Отрицательная + + + + + Биполярная + + + + + + + 240 + 430 + 81 + 21 + + + + 100 + + + 1000 + + + 200 + + + + + + 20 + 430 + 201 + 22 + + + + Время слепоты после разряда, мс + + + Qt::AlignCenter + + + + + + 20 + 460 + 201 + 22 + + + + Таймаут отключения , с + + + Qt::AlignCenter + + + + + + 240 + 460 + 81 + 21 + + + + 10 + + + 3600 + + + 10 + + + 30 + + + + + + 20 + 490 + 201 + 22 + + + + Размер буффера редетекции + + + Qt::AlignCenter + + + + + + 240 + 490 + 81 + 21 + + + + 4 + + + 10 + + + 10 + + + + + + 20 + 520 + 201 + 22 + + + + Порог буффера редетекции + + + Qt::AlignCenter + + + + + + 240 + 520 + 81 + 21 + + + + 2 + + + 8 + + + 8 + + + + + + 220 + 210 + 91 + 16 + + + + + 75 + true + + + + Терапия + + + Qt::AlignCenter + + + + + + 320 + 260 + 171 + 341 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 161 + 22 + + + + Текущий режим терапии + + + Qt::AlignCenter + + + + + + 10 + 20 + 151 + 31 + + + + + + + + + + 10 + 50 + 121 + 22 + + + + Текущий подрежим + + + Qt::AlignCenter + + + + + + 10 + 70 + 151 + 31 + + + + + + + + + + 0 + 100 + 161 + 22 + + + + Мгновенный период + + + Qt::AlignCenter + + + + + + 50 + 120 + 51 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 130 + 161 + 22 + + + + Усреднёный период + + + Qt::AlignCenter + + + + + + 50 + 150 + 61 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 160 + 161 + 22 + + + + Счётчик Vs + + + Qt::AlignCenter + + + + + + 30 + 180 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 190 + 161 + 22 + + + + Счётчик Vp + + + Qt::AlignCenter + + + + + + 30 + 210 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 440 + 161 + 22 + + + + Счётчик Vn + + + Qt::AlignCenter + + + + + + 10 + 470 + 111 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 220 + 161 + 22 + + + + U на батарее, В + + + Qt::AlignCenter + + + + + + 30 + 240 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 280 + 161 + 22 + + + + U на конденсаторе, В + + + Qt::AlignCenter + + + + + + 30 + 300 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 250 + 161 + 22 + + + + U на батарее, % + + + Qt::AlignCenter + + + + + + 30 + 270 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 30 + 230 + 231 + 21 + + + + + 75 + true + true + + + + Текущий режим работы стимулятора + + + Qt::AlignCenter + + + + + + 270 + 230 + 221 + 22 + + + + + 8 + + + + true + + + без стимуляции + + + 0 + + + + без стимуляции + + + + + нормальный + + + + + с принудительной стимуляцией + + + + + + + 20 + 550 + 201 + 22 + + + + Коэффициент усиления + + + Qt::AlignCenter + + + + + + 240 + 550 + 81 + 21 + + + + 0 + + + 255 + + + 0 + + + + + + + 20 + 790 + 1371 + 161 + + + + + + + 20 + 10 + 141 + 61 + + + + + 10 + + + + Чтение из + + + false + + + + + 10 + 20 + 111 + 17 + + + + + 8 + + + + файла + + + + + + 10 + 40 + 101 + 17 + + + + + 8 + + + + com-порта + + + true + + + + + + true + + + + 1340 + 40 + 151 + 23 + + + + Перезагрузка + + + + + + 20 + 950 + 1371 + 16 + + + + Qt::Horizontal + + + + + + 1020 + 0 + 361 + 21 + + + + QFrame::Box + + + 0 + + + + + + Qt::AlignCenter + + + + + + 500 + 0 + 75 + 23 + + + + Старт + + + + + + 580 + 0 + 75 + 23 + + + + Стоп + + + + + + 1500 + 40 + 111 + 22 + + + + Запись на SD-карту + + + Qt::AlignCenter + + + + + + 1620 + 40 + 51 + 20 + + + + нет + + + 0 + + + + нет + + + + + да + + + + + + + 1780 + 40 + 91 + 20 + + + + 433 + + + 0 + + + + 433 + + + + + Bluetooth + + + + + + + 1680 + 40 + 101 + 22 + + + + Режим передачи + + + Qt::AlignCenter + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000..fd4d4cf Binary files /dev/null and b/logo.ico differ diff --git a/project/__init__.py b/project/__init__.py new file mode 100644 index 0000000..704f64b --- /dev/null +++ b/project/__init__.py @@ -0,0 +1,8 @@ +"""Режимы работы, переменные, влияющие на работу, которые с некоторой вероятностью будут меняться.""" + + +DATA_RATE = 200 # скорость передачи данных + +# mes = "AT+CONA11899AA1670B\r\n" +mes = "AT+CONA11899AA21438\r\n" # для связи со стимулятором + diff --git a/project/controller.py b/project/controller.py new file mode 100644 index 0000000..7b879bd --- /dev/null +++ b/project/controller.py @@ -0,0 +1,472 @@ +"""Преобразование данных""" + +import struct + +import numpy as np + +from project import DATA_RATE +import model + +NORMAL_LENGTH = 32 # размер пакета +Start_byte = b'\x55' # стартовый байт, нужен для парсинга сообщения +Stop_byte = b'\x77' # стоповый байт, нужен для парсинга сообщения + +threshold_MIN = 100 # минимално возможная амплитуда выше которой ищем пики в базовом режиме +threshold_MAX = 400 # максимально возможная амплитуда выше которой не сохраняем + + +# методы чтения передаваемых данных +def shift_or(one, two, three, four): + """Чтение 4-ёх байтного параметра""" + a = one << 24 | two << 16 | three << 8 | four + return a + + +def shift_or_24(two, three, four): + """Чтение 3-ёх байтного параметра""" + a = two << 16 | three << 8 | four + return a + + +def shift_or_16(three, four): + """Чтение 2-ух байтного параметра""" + a = three << 8 | four + return a + + +def get_data_bin(file, data_in): + """ + Получение данных из бинарного файла - записанных данных + :param file: файл для чтения + :param data_in: объект для хранения модели данных + """ + chunk = list(model.get_data_bin(file, data_in)) + data_in.chunk = [item.to_bytes() for item in list(chunk)] + + +def write_file(): + """ + Открытие на запись файла + """ + return model.write_file() + + +def write_in_file(file, rx): + """ + Запись данных в файл + :param file: файл + :param rx: серия данных + """ + model.write_in_file(file, rx) + + +def close_file(file): + """ + Закрыть файл + :param file: файл + """ + return model.close_file(file) + + +def create_mess(send_mode, param_code, param, size, type_param): + """ + Создание сообщения для передачи параметров + :param send_mode: режим передачи данных 433 - 0(Bluetooth - 1) + :param param_code: код для идентификации параметра для передачи(задано протоколом взаимодействия) + :param param: передаваемое значение + :param size: размер передаваемого значения + :param type_param: тип передаваемого значения + """ + index = 2 + if send_mode: + index = 10 + init_mes = [0x41, 0x54, 0x2b, 0x44, 0x41, 0x54, 0x41, 0x31, 0x55, param_code, 0x77, 0x0D, 0x0A] + else: + init_mes = [0x55, param_code, 0x77] + param_in_bytes = [] + if type_param == float: + param = struct.unpack('i', struct.pack('f', param))[0] + param_in_bytes = [int(((param & 0xFF000000)) >> 24), int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if type_param == int: + if size == 4: + param_in_bytes = [int(((param & 0xFF000000)) >> 24), int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 3: + param_in_bytes = [0, int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 2: + param_in_bytes = [0, 0, int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 1: + param_in_bytes = [0, 0, 0, int((param & 0xFF))] + + for _ in param_in_bytes[::-1]: + init_mes.insert(index, _) + + mes = bytes(init_mes) + return mes + + +def state_packet(send_mode, serial, param_code, param, size, type_param): + """ + Отправка пакета параметров + :param send_mode: режим передачи данных 433 - 0(Bluetooth - 1) + :param serial: канал передачи + :param param_code: код для идентификации параметра для передачи(задано протоколом взаимодействия) + :param param: передаваемое значение + :param size: размер передаваемого значения + :param type_param: тип передаваемого значения + """ + if serial.isOpen(): + serial.writeData(create_mess(send_mode, param_code, param, size, type_param)) + + +class DataClass: + """ + Класс модель данных для отображения на графике + """ + + def __init__(self): + # переменные для работы с потоком данных с устройства + self.data_rx = [] # список в котором храним данные + self.draw_cnt = np.uint16(0) # x-координата точки в текущий момент + self.data_length = 0 # кол-во отсчетов для отрисовки графика по x-координате + self.pac_count = 0 # счетчик для распознавания пакетов + self.counter = [] # счетчик небитых пакетов + + # переменные для работы с записанным файлом + self.i = 0 # счетчик для чтения файла + self.chunk = [] # считанный массив из файла + + # переменные для отрисовки графиков + + # Сигнал по которому определяют ЧСС ЭКГ сигнал если у нас кардиограф + # Правый желудочек (используется основной канал) + self.ECG_RA = [] # np.zeros(self.data_length, np.int16) + # позитивно отраженный сигнал + self.ECG_RA_pos_sig = [] # np.zeros(self.data_length, np.int16) + # динамический порог рассчитываемый в зависимости от нижнего и верхнего порогов + self.ECG_RA_din_threshold = [] # np.zeros(self.data_length, np.int16) + # нижний порог порог для метода треугольников при его пересечении считаем ЧСС событие + self.ECG_RA_min_threshold = np.int16(threshold_MIN) + # верхний порог для метода треугольников выше него мы не берём данные + self.ECG_RA_max_threshold = np.int16(threshold_MAX) + self.Last_RR_poz_rel = [] # время до прошлого события + self.text_list = {} # список label на графике для динамического обновления + self.last_period = [] # мгновенный период для отрисовки label + self.rr_now = [] # есть ли удар сердца в текущий момент + self.last_QRS = [] # тип последнего события Vsense = 0 нормальное сокращение + # Vpace = 1 пришлось стимулировать сейчас не используем + # Vnoise = 2 шумы в QRS комплексе(пока не реализовали) + + # переменные для отрисовки label (обозначение на графике событий) + self.V_print = 0 # необходимость отображения события + self.draw_cnt_last = 0 # x-координата события + self.draw_last_period = 0 # мгновенный период для отображения + self.draw_last_QRS = 0 # тип последнего события для отображения + + # переменные для передачи параметров стимулятору и отображения текущих значений + self.dc_cut = 1 # вывод сигнала с постоянной составляющей или без + self.channel = 3 # активный канал передачи + self.signal = 0 # вывод сигнала с фильтром или без + self.sd_card = 0 # запись на sd-карту + self.hv_volt = 0 # напряжение на конденсаторе + self.bat_volt = 0 # напряжение на батарее + self.bat_pers = 0 # напряжение на батарее в процентах + self.spi_pot_set = 0 # коэффициент усиления + self.Work_Mode_pacemaker = 0 # текущий режим работы стимулятора + self.redet_num = 0 # размер буфера редетекции + self.redet_bad = 0 # порог буфера редетекции + self.standby_timer = 0 # таймаут отключения + self.hv_blind_time = 0 # время слепоты после разряда + self.max_energy = 0 # максимальная энергия + self.min_energy = 0 # минимальная энергия + self.hv_step_number = 0 # кол-во ступеней высоковольтной терапии + self.fibr_max_tres = 0 # порог корзины фибриляций + self.tachy_2_tres = 0 # пороговое значение для тахикардии 2ст + self.tachy_1_tres = 0 # пороговое значение для тахикардии 1ст + self.fibr_tres = 0 # пороговое значение для фибрилляции + self.max_search_time = 135 # длительность поиска максимума + self.square_coef = 0.75 # порог чувствительности Т волны + self.square_time = 350 # длительность защиты от T волны + self.max_treshold = 8.0 # максимальная чувствительность + self.min_treshold = 2.0 # минимальная чувствительность + self.max_time = 1250 # максимальный размер RR интервала + self.fibr_cnt = 0 # кол-во событий фибрилляции (прогресс-бар) + self.norm_cnt = 0 # кол-во нормальных сокращений (прогресс-бар) + self.tachy_1_cnt = 0 # кол-во событий тахикардии 1ст (прогресс-бар) + self.tachy_2_cnt = 0 # кол-во событий тахикардии 2ст (прогресс-бар) + self.hv_step_cnt = 0 # текущая ступень ВВ терапии + self.last_period_stat = 0 # мгновенный период + self.filt_period = 0 # усредненный период + self.now_energy = 0 # текущая энергия + self.Vn_cnt = 0 # счетчик Vn + self.Vp_cnt = 0 # счетчик Vp + self.Vs_cnt = 0 # счетчик Vs + self.sub_mode = 0 # текущий подрежим терапии + self.terapy_now = 0 # текущий режим терапии + + def clear_data(self): + """ + Очистить модель хранения данных + """ + self.__init__() + # self.data_rx.clear() + # self.data_length = 0 + # self.counter = np.zeros(self.data_length, np.int16) # np.zeros(self.data_length, np.int16) + # self.pac_count = 0 + # self.draw_cnt = 0 + # + # self.i = 0 + # self.chunk = [] + # self.update_data(self.data_length) + + # # Сигнал по которому определяют ЧСС ЭКГ сигнал если у нас кардиограф и производная фильрованного ФПГ если часы + # # Правый желудочек (используется основной канал) + # self.ECG_RA = [] + # + # # позитивно отраженный сигнал + # self.ECG_RA_pos_sig = [] # np.zeros(self.data_length, np.int16) + # + # # динамический порог рассчитываемый в зависимости от нижнего и верхнего порогов + # self.ECG_RA_din_threshold = [] # np.zeros(self.data_length, np.int16) + # + # # нижний порог порог для метода треугольников при его пересечении считаем ЧСС событие + # self.ECG_RA_min_threshold = np.int16(threshold_MIN) + # + # # верхний порог для метода треугольников выше него мы не берём данные + # self.ECG_RA_max_threshold = np.int16(threshold_MAX) + + # счетчик пакетов для вывод на график(визуальный контроль) + + def update_data(self, data_length): + """ + Создание нулевых массивов для хранения в них даных для отображения + :param data_length: длина массива (сколько значений поместится в окно) + """ + self.ECG_RA = np.zeros(data_length, np.float32) + self.ECG_RA_pos_sig = np.zeros(data_length, np.float32) + self.ECG_RA_din_threshold = np.zeros(data_length, np.float32) + self.counter = np.zeros(data_length, np.float32) + self.ECG_RA_min_threshold = np.zeros(data_length, np.float32) + self.ECG_RA_max_threshold = np.zeros(data_length, np.float32) + self.Last_RR_poz_rel = np.zeros(data_length, np.uint16) + self.last_QRS = np.zeros(data_length, np.uint16) + self.rr_now = np.zeros(data_length, np.uint16) + self.last_period = np.zeros(data_length, np.uint16) + + def parse_list(self, data_in): + """ + Расшифровка полученной серии данных + :param data_in: ссылка на объект модели данных + """ + self.data_rx = self.data_rx + list(data_in) + + if len(self.data_rx) >= NORMAL_LENGTH * 2: # парсить имеет смысл при длинне пакета больше минимальной + while self.pac_count <= len(self.data_rx) - NORMAL_LENGTH: + if (self.data_rx[self.pac_count] == Start_byte) and ( + self.data_rx[self.pac_count + NORMAL_LENGTH - 1] == Stop_byte) \ + and ((len(self.data_rx) - self.pac_count) >= NORMAL_LENGTH + 0): + self.draw_cnt += 1 + if self.draw_cnt >= self.data_length: + self.draw_cnt = 0 + self.text_list = {} + + self.parse_part_from_1_to_20_byte() + + if self.draw_cnt > 1: + self.ECG_RA_min_threshold[self.draw_cnt] = self.ECG_RA_min_threshold[self.draw_cnt - 1] + self.ECG_RA_max_threshold[self.draw_cnt] = self.ECG_RA_max_threshold[self.draw_cnt - 1] + self.last_period[self.draw_cnt] = self.last_period[self.draw_cnt - 1] + else: + self.ECG_RA_min_threshold[self.draw_cnt] = self.ECG_RA_min_threshold[self.data_length - 1] + self.ECG_RA_max_threshold[self.draw_cnt] = self.ECG_RA_max_threshold[self.data_length - 1] + self.last_period[self.draw_cnt] = self.last_period[self.data_length - 1] + + self.parse_part_from_23_to_30_byte() + + if self.rr_now[self.draw_cnt] == 1: + self.V_print = 1 + self.draw_cnt_last = self.draw_cnt + self.draw_last_period = self.last_period[self.draw_cnt] + + # в конце парсинга шагаем по массиву + self.pac_count += NORMAL_LENGTH - 1 + + del self.data_rx[0:self.pac_count] + self.pac_count = 0 + self.pac_count += 1 + + def parse_part_from_1_to_20_byte(self): + """ + Парсинг с 1 по 20 байт + """ + self.counter[self.draw_cnt] = shift_or(int.from_bytes(self.data_rx[self.pac_count + 1], "big"), + int.from_bytes(self.data_rx[self.pac_count + 2], "big"), + int.from_bytes(self.data_rx[self.pac_count + 3], "big"), + int.from_bytes(self.data_rx[self.pac_count + 4], "big")) + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 5], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 6], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 7], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 8], "big", signed=False))) + self.ECG_RA_din_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 9], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 10], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 11], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 12], "big", signed=False))) + self.ECG_RA_pos_sig[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 13], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 14], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 15], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 16], "big", signed=False))) + self.ECG_RA[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + self.Last_RR_poz_rel[self.draw_cnt] = shift_or_16( + int.from_bytes(self.data_rx[self.pac_count + 17], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 18], "big")) + + int1 = int.from_bytes(self.data_rx[self.pac_count + 19], "big") + self.terapy_now = int1 & 0x3 + self.sub_mode = (int1 & 0x1C) >> 2 + + int1 = int.from_bytes(self.data_rx[self.pac_count + 20], "big") + self.last_QRS[self.draw_cnt] = int1 & 0x3 + self.draw_last_QRS = int1 & 0x3 + self.rr_now[self.draw_cnt] = (int1 & 0x4) >> 2 + self.Work_Mode_pacemaker = (int1 & 0x60) >> 5 + self.signal = (int1 & 0x80) >> 7 + + def parse_part_from_23_to_30_byte(self): + """ + Парсинг с 23 по 30 байт (передача значений в зависимости от остатка от деления счетчика) + """ + if self.counter[self.draw_cnt] % 10 == 0: + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + self.ECG_RA_min_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + self.min_treshold = self.ECG_RA_min_threshold[self.draw_cnt] + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.ECG_RA_max_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + self.max_treshold = self.ECG_RA_max_threshold[self.draw_cnt] + + if self.counter[self.draw_cnt] % 10 == 1: + int1 = ( + shift_or(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + self.square_coef = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + if self.counter[self.draw_cnt] % 10 == 2: + self.square_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.max_search_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 3: + int1 = int.from_bytes(self.data_rx[self.pac_count + 24], "big") + self.sd_card = (int1 & 0x4) >> 2 + self.channel = int1 & 0x3 + self.dc_cut = (int1 & 0x8) >> 3 + self.spi_pot_set = int.from_bytes(self.data_rx[self.pac_count + 25], "big") + self.bat_pers = int.from_bytes(self.data_rx[self.pac_count + 26], "big") + self.bat_volt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + self.hv_volt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 4: + self.last_period[self.draw_cnt] = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + self.last_period_stat = self.last_period[self.draw_cnt] + self.max_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 5: + self.Vs_cnt = ( + shift_or_24(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False))) + self.Vp_cnt = ( + shift_or_24(int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.Vn_cnt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + )) + + if self.counter[self.draw_cnt] % 10 == 6: + self.fibr_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False) + )) + self.tachy_2_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False) + )) + self.tachy_1_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False) + )) + + if self.counter[self.draw_cnt] % 10 == 7: + self.fibr_cnt = int.from_bytes(self.data_rx[self.pac_count + 25], "big") + self.tachy_2_cnt = int.from_bytes(self.data_rx[self.pac_count + 26], "big") + self.tachy_1_cnt = int.from_bytes(self.data_rx[self.pac_count + 27], "big") + self.norm_cnt = int.from_bytes(self.data_rx[self.pac_count + 28], "big") + self.fibr_max_tres = int.from_bytes(self.data_rx[self.pac_count + 29], "big") + self.filt_period = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 8: + self.now_energy = (shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", + signed=False))) / 10 + self.min_energy = (int.from_bytes(self.data_rx[self.pac_count + 26], "big", + signed=False)) // 10 + self.max_energy = (shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", + signed=False))) // 10 + int1 = int.from_bytes(self.data_rx[self.pac_count + 23], "big") + self.hv_step_cnt = int1 & 0xF + self.hv_step_number = (int1 & 0xF0) >> 4 + + if self.counter[self.draw_cnt] % 10 == 9: + self.hv_blind_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False))) + self.standby_timer = ( + shift_or(int.from_bytes(self.data_rx[self.pac_count + 27], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", + signed=False) + )) // 1000 + int1 = int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False) + self.redet_bad = int1 & 0xF + self.redet_num = (int1 & 0xF0) >> 4 diff --git a/project/main.py b/project/main.py new file mode 100644 index 0000000..ecd3ace --- /dev/null +++ b/project/main.py @@ -0,0 +1,15 @@ +""" +Запуск программы + Два режим работы: + 1. чтение из com-порта, отображение, запись данных, передача настраиваемых параметров в стимулятор, + управелние перезагрузкой + 2. чтение данных из файла и отображение +""" + +import project.view + + +app = project.view.app +window = project.view.Ui() +window.show() +app.exec_() diff --git a/project/model.py b/project/model.py new file mode 100644 index 0000000..902a9f4 --- /dev/null +++ b/project/model.py @@ -0,0 +1,58 @@ +"""Получение сырых данных из различных источников. Работа с файлами""" +import datetime +import os.path + +# from PyQt5.QtSerialPort import QSerialPort + + +# def on_read(): +# +# +# +# # создание соединения с устройством для передачи данных +# serial = QSerialPort() +# serial.setBaudRate(115200) +# serial.readyRead.connect(on_read) + + +def get_data_bin(file_name, data_in): + """ + Получение данных из бинарного файла + :param file_name: файл + :param data_in: объект модели данных для отрисовки + :return: массив данных + """ + f = open(file_name, 'rb') + size = os.path.getsize(file_name) + chunk = f.read(size) + data_in.data_length = int(size / 32) + data_in.update_data(data_in.data_length) + f.close() + return chunk + + +def write_file(): + """ + Запись файла + """ + # создание папки data(если её нет) + if not os.path.isdir("data"): + os.mkdir("data") + + file_name = datetime.datetime.now().strftime('%d%m%y%H%M%S') + file = open('data/' + file_name + '.txt', 'wb') + return file, file_name + + +def write_in_file(file, rx): + file.write(rx) + + +def close_file(file): + """ + Закончить запись и закрыть файл + """ + if file != "": + file.close() + + return "" diff --git a/project/view.py b/project/view.py new file mode 100644 index 0000000..7eaf853 --- /dev/null +++ b/project/view.py @@ -0,0 +1,626 @@ +"""Отображение данных""" + +import sys +import os.path +import datetime +import time + +import numpy as np + +from PyQt5.QtCore import QIODevice +from PyQt5.QtSerialPort import QSerialPortInfo, QSerialPort + +from PyQt5.QtGui import * +from PyQt5 import QtWidgets, uic, QtGui +from PyQt5.QtWidgets import QFileDialog +from PyQt5 import QtCore +from pyqtgraph import TextItem +import pyqtgraph as pg +import project.controller as my_data +from project import mes + +# выбор режима чения из файла или из com-порта +MODE_COM_PORT = False +MODE_FILE = False + +# объявили экземпляры класса, для обработки данных из файла или из com-порта +data_IN_FILE = my_data.DataClass() +data_IN_COM = my_data.DataClass() + +# создание приложения +app = QtWidgets.QApplication(sys.argv) + + +class Ui(QtWidgets.QMainWindow): + """Класс отображения данных""" + + def __init__(self): + # создание графического отображения + super(Ui, self).__init__() + uic.loadUi('design_viewer.ui', self) + self.setWindowTitle("Pacemaker viewer") + self.setWindowIcon(QtGui.QIcon("logo.ICO")) + + self.counter_com_port = [] # координата x для вывода графиков + + self.point = 0 # координата, откуда начинать отрисовку, если используется полоса прокрутки?? + self.file = "" # файл + self.file_name = "" # наименование файла + + # Создание графиков + self.graph_init() + + def set_up_controls(): + """ + Настройка элементов управления в визуале + """ + self.OpenB.setVisible(False) + self.CloseB.setVisible(False) + self.Play.setVisible(False) + self.Pause.setVisible(False) + self.horizontalScrollBar.setVisible(False) + self.horizontalScrollBar.valueChanged.connect(self.on_scroll) + self.radioButton_work_mode_com_port.toggled.connect(self.check_work_mode_viewer) + self.OpenB.clicked.connect(self.on_open) + self.CloseB.clicked.connect(self.on_close) + self.Write_file.clicked.connect(self.write_file) + self.Close_file.clicked.connect(self.close_file) + self.Read.clicked.connect(self.on_start) + self.Stop.clicked.connect(self.on_stop) + self.Restart.clicked.connect(self.restart) + self.SD_card_Box.currentTextChanged.connect(self.set_SD_card) + self.Send_mode_Box.currentTextChanged.connect(self.set_send_mode) + self.ComL.addItems(self.update_list()) + self.horizontalScrollBar.valueChanged.connect(self.on_scroll) + self.RA_scale_BOX.currentTextChanged.connect(self.on_scale_change) + self.Low_V_spinBox.valueChanged.connect(self.set_fibr_cnt_max) + self.Low_V_spinBox_3.valueChanged.connect(self.set_tachy_2_cnt_max) + self.Low_V_spinBox_4.valueChanged.connect(self.set_tachy_1_cnt_max) + + self.RA_max_time_ms_BOX.editTextChanged.connect(self.set_RA_max_time_ms_BOX) + self.RA_min_sensitivity_BOX.editTextChanged.connect(self.set_RA_min_sensitivity_BOX) + self.RA_max_sensitivity_BOX.editTextChanged.connect(self.set_RA_max_sensitivity_BOX) + self.RA_square_time_ms_BOX.editTextChanged.connect(self.set_RA_square_time_ms_BOX) + self.RA_square_coeff_BOX.editTextChanged.connect(self.set_RA_square_coeff_BOX) + self.RA_all_time_ms_BOX.editTextChanged.connect(self.set_RA_all_time_ms_BOX) + self.Signal_Box.currentIndexChanged.connect(self.set_Signal_Box) + self.Channel_Box.currentIndexChanged.connect(self.set_Channel_Box) + self.DC_cut_Box.currentIndexChanged.connect(self.set_DC_cut_Box) + + self.High_Tf_spinBox.valueChanged.connect(self.set_High_Tf_spinBox) + self.High_Tt2_spinBox.valueChanged.connect(self.set_High_Tt2_spinBox) + self.High_Tt1_spinBox.valueChanged.connect(self.set_High_Tt1_spinBox) + self.Low_V_spinBox.valueChanged.connect(self.set_Low_V_spinBox) + + self.Work_Mode_pacemaker.editTextChanged.connect(self.set_Work_Mode_pacemaker) + self.hv_step_number_spinBox.valueChanged.connect(self.set_hv_step_number_spinBox) + self.min_energy_spinBox.valueChanged.connect(self.set_min_energy_spinBox) + self.max_energy_spinBox.valueChanged.connect(self.set_max_energy_spinBox) + self.hv_blind_time_spinBox.valueChanged.connect(self.set_hv_blind_time_spinBox) + self.standby_timer_spinBox.valueChanged.connect(self.set_standby_timer_spinBox) + self.redet_num_spinBox.valueChanged.connect(self.set_redet_num_spinBox) + self.redet_bad_spinBox.valueChanged.connect(self.set_redet_bad_spinBox) + self.Spi_spot_set_spinBox.valueChanged.connect(self.set_Spi_spot_set_spinBox) + + set_up_controls() + + # создание соединения с устройством для передачи данных + self.serial = QSerialPort() + self.serial.setBaudRate(115200) + self.serial.readyRead.connect(self.on_read) + + self.send_mode = 0 + + # Создание таймера для отрисовки графиков + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.timerEvent) + self.timer.start(20) + + # Создание таймера для отображения параметров стимулятора + self.timer_LS = QtCore.QTimer() + self.timer_LS.timeout.connect(self.timerEvent_param) + self.timer_LS.start(2000) + + def write_file(self): + """ + Открытие на запись файла + """ + self.file, file_name = my_data.write_file() + self.Write_file.setStyleSheet('background-color: green; color: white') + self.Fname_w_label.setText(os.path.abspath(file_name)) + + def close_file(self): + """ + Закончить запись и закрыть файл + """ + self.Fname_w_label.setText('') + self.Write_file.setStyleSheet('background-color: grey; color: black') + self.file = my_data.close_file(self.file) + + def restart(self): + """ + Перезагрузка устройства + """ + param = 1 + my_data.state_packet(self.send_mode, self.serial, 0x1D, param, 1, int) + + def timerEvent_param(self): + """Запуск по таймеру, для синхронизации параметров стимулятора""" + if MODE_COM_PORT: + self.synchronise_param(data_IN_COM) + if MODE_FILE: + self.synchronise_param(data_IN_FILE) + # отрисовка + app.processEvents() + + def synchronise_param(self, data_in): + """Синхронизация параметров стимулятора""" + self.SD_card_Box.setCurrentIndex(int(data_in.sd_card)) + self.Signal_Box.setCurrentIndex(int(data_in.signal)) + self.Channel_Box.setCurrentIndex(int(data_in.channel)) + self.DC_cut_Box.setCurrentIndex(int(data_in.dc_cut)) + self.RA_max_time_ms_BOX.setEditText(str(data_in.max_search_time)) + self.RA_min_sensitivity_BOX.setEditText(str(data_in.min_treshold)) + self.RA_max_sensitivity_BOX.setEditText(str(data_in.max_treshold)) + self.RA_square_time_ms_BOX.setEditText(str(data_in.square_time)) + self.RA_square_coeff_BOX.setEditText(f'{data_in.square_coef: .2f}') + self.RA_all_time_ms_BOX.setEditText(str(data_in.max_time)) + self.High_Tf_spinBox.setValue(data_in.fibr_tres) + self.High_Tt2_spinBox.setValue(data_in.tachy_2_tres) + self.High_Tt1_spinBox.setValue(data_in.tachy_1_tres) + self.Low_V_spinBox.setValue(data_in.fibr_max_tres) + self.hv_step_number_spinBox.setValue(data_in.hv_step_number) + self.min_energy_spinBox.setValue(data_in.min_energy) + self.max_energy_spinBox.setValue(data_in.max_energy) + self.hv_blind_time_spinBox.setValue(data_in.hv_blind_time) + self.standby_timer_spinBox.setValue(data_in.standby_timer) + self.redet_num_spinBox.setValue(data_in.redet_num) + self.redet_bad_spinBox.setValue(data_in.redet_bad) + self.Work_Mode_pacemaker.setCurrentIndex(data_in.Work_Mode_pacemaker) + self.Spi_spot_set_spinBox.setValue(data_in.spi_pot_set) + + def timerEvent(self): + if MODE_COM_PORT: + self.draw_data(data_IN_COM) + if MODE_FILE: + scale = self.get_scale() + data_IN_FILE.data_length = scale * my_data.DATA_RATE + data_IN_FILE.parse_list(data_IN_FILE.chunk[data_IN_FILE.i:data_IN_FILE.i + 640]) + data_IN_FILE.i += 640 + self.graph_1.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.graph_2.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.graph_3.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.draw_data(data_IN_FILE) + + # отрисовка + app.processEvents() + + def on_start(self): + """ + Старт обменя с com-портом + """ + global MODE_COM_PORT + data_IN_COM.clear_data() + self.serial.setPortName(self.ComL.currentText()) + self.serial.open(QIODevice.ReadWrite) + self.serial.flush() + self.serial.write(mes.encode()) + self.on_close() + MODE_COM_PORT = True + self.graph_init() + scale = self.get_scale() + data_IN_COM.data_length = scale * my_data.DATA_RATE + data_IN_COM.update_data(data_IN_COM.data_length) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_COM.data_length))) + + def graph_init(self): + """ + Создние графиков + """ + # Создание графиков + self.curve10 = self.graph_1.plot(pen=(0, 0, 255)) + self.curve20 = self.graph_2.plot(pen=(0, 0, 0)) + pen_din_tres = pg.mkPen(color=(255, 0, 0), width=1) + self.curve21 = self.graph_2.plot(pen=pen_din_tres) + pen_peak = None + self.curve22 = self.graph_2.plot(pen=pen_peak) + self.curve23 = self.graph_2.plot(pen=pen_peak) + pen_min_tres = pg.mkPen(color=(0, 255, 0), width=2, style=QtCore.Qt.DashLine) + pen_max_tres = pg.mkPen(color=(0, 0, 255), width=2, style=QtCore.Qt.DashLine) + self.curve24 = self.graph_2.plot(pen=pen_min_tres) + self.curve25 = self.graph_2.plot(pen=pen_max_tres) + self.curve26 = self.graph_2.plot(pen=(0, 0, 0)) + self.curve30 = self.graph_3.plot(pen=(0, 0, 255)) + + def edit_graph(graph): + """ + Форматирование полей графиков + :param graph: поле графика + """ + graph.showGrid(x=True, y=True) + graph.setBackground('w') + # делаем поле графика нечувствительным к мышке + graph.setMouseEnabled(x=False, y=False) + graph.hideButtons() + graph.getPlotItem().setMenuEnabled(False) + graph.addLegend(enableMouse=False) + + edit_graph(self.graph_1) + edit_graph(self.graph_2) + edit_graph(self.graph_3) + + def graph_clear(self): + """ + Очистка графиков + """ + self.graph_1.clear() + self.graph_2.clear() + self.graph_3.clear() + + def on_read(self): + """ + Чтение серии данных + """ + rx = self.serial.readAll() + if self.file != "": + my_data.write_in_file(self.file, rx) + data_IN_COM.parse_list(rx) + + def draw_data(self, data_in): + """ + Отрисовка данных + :param data_in: модель данных + """ + self.curve10.setData(self.counter_com_port, data_in.ECG_RA) + self.curve20.setData(self.counter_com_port, data_in.ECG_RA_pos_sig) + self.curve21.setData(self.counter_com_port, data_in.ECG_RA_din_threshold) + self.curve26.setData(self.counter_com_port, np.zeros(len(data_in.ECG_RA))) + self.curve24.setData(self.counter_com_port, data_in.ECG_RA_min_threshold) + self.curve25.setData(self.counter_com_port, data_in.ECG_RA_max_threshold) + self.curve30.setData(self.counter_com_port, data_in.Last_RR_poz_rel) + self.Vs_cnt_label.setText(str(data_in.Vs_cnt)) + self.Vp_cnt_label.setText(str(data_in.Vp_cnt)) + self.Vn_cnt_label.setText(str(data_in.Vn_cnt)) + self.U_batt_label.setText(str(float(data_in.bat_volt / 1000))) + self.U_batt_p_label.setText(str(data_in.bat_pers)) + self.U_cap_label.setText(str(float(data_in.hv_volt / 100))) + self.now_energy_label.setText(str(data_in.now_energy)) + self.filt_period_label.setText(str(data_in.filt_period)) + self.last_period_label.setText(str(data_in.last_period_stat)) + self.hv_step_cnt_label.setText(str(data_in.hv_step_cnt)) + + self.progressBar_f.setValue(data_in.fibr_cnt) + self.progressBar_t2.setValue(data_in.tachy_2_cnt) + self.progressBar_t1.setValue(data_in.tachy_1_cnt) + self.progressBar_n.setValue(data_in.norm_cnt) + + if data_in.terapy_now == 0: + self.terapy_nowB.setStyleSheet('background-color: green; color: white') + self.terapy_nowB.setText("Поиск корзин") + if data_in.terapy_now == 1: + self.terapy_nowB.setStyleSheet('background-color: red; color: white') + self.terapy_nowB.setText("Терапия фибрилляции") + + if data_in.terapy_now == 2: + self.terapy_nowB.setStyleSheet('background-color: yellow; color: black') + self.terapy_nowB.setText("Терапия сильной тахикардии") + + if data_in.terapy_now == 3: + self.terapy_nowB.setStyleSheet('background-color: yellow; color: black') + self.terapy_nowB.setText("Терапия слабой тахикардии") + + if data_in.sub_mode == 0: + self.sub_modeB.setStyleSheet('background-color: green; color: white') + self.sub_modeB.setText("Терапия неактивна") + if data_in.sub_mode == 1: + self.sub_modeB.setStyleSheet('background-color: blue; color: white') + self.sub_modeB.setText("Зарядка конденсатора") + + if data_in.sub_mode == 2: + self.sub_modeB.setStyleSheet('background-color: yellow; color: black') + self.sub_modeB.setText("Редетекция") + + if data_in.sub_mode == 3: + self.sub_modeB.setStyleSheet('background-color: red; color: white') + self.sub_modeB.setText("Стимуляция") + if data_in.sub_mode == 4: + self.sub_modeB.setStyleSheet('background-color: grey; color: black') + self.sub_modeB.setText("Терапия не сработала") + + # так удаляются элементы + # стирание элементов спереди + all_items = self.graph_1.items() # Получаем все элементы + text_items = [item for item in all_items if isinstance(item, TextItem)] + for text_item in text_items: + if data_in.draw_cnt / my_data.DATA_RATE - 0.02 < text_item.pos().x() and data_in.draw_cnt / my_data.DATA_RATE + 0.1 > text_item.pos().x(): + self.graph_1.removeItem(text_item) + + if data_in.V_print: + data_in.V_print = 0 + if data_in.draw_last_QRS == 0: + self.text = pg.TextItem('Vs\nT:' + str(data_in.draw_last_period)) + self.text.setColor('green') + self.text.setFont(QFont('Arial', 7)) + self.text.setPos(data_in.draw_cnt_last / my_data.DATA_RATE, (0 if data_in.dc_cut else 15)) + self.graph_1.addItem(self.text) + if data_in.draw_last_QRS == 1: + self.text = pg.TextItem('Vp\nT:' + str(data_in.draw_last_period)) + self.text.setColor('red') + self.text.setPos(data_in.draw_cnt_last / my_data.DATA_RATE, (0 if data_in.dc_cut else 15)) + self.graph_1.addItem(self.text) + + def on_stop(self): + self.close_file() + global MODE_COM_PORT + MODE_COM_PORT = False + self.graph_clear() + # Создание графиков + self.graph_init() + self.serial.flush() + self.serial.close() + data_IN_COM.clear_data() + self.terapy_nowB.setStyleSheet('background-color: lightGray; color: black') + self.terapy_nowB.setText("") + self.sub_modeB.setStyleSheet('background-color: lightGray; color: black') + self.sub_modeB.setText("") + self.last_period_label.setText("") + self.filt_period_label.setText("") + self.Vs_cnt_label.setText("") + self.Vp_cnt_label.setText("") + self.Vn_cnt_label.setText("") + self.progressBar_f.setValue(0) + self.progressBar_t2.setValue(0) + self.progressBar_t1.setValue(0) + self.progressBar_n.setValue(0) + self.hv_step_cnt_label.setText("") + self.now_energy_label.setText("") + self.HR_label.setText("") + self.OpenB.setEnabled(True) + self.CloseB.setEnabled(True) + + def on_open(self): + """ + Открыть файл для чтения + """ + global MODE_FILE + self.on_close() + file, _ = QFileDialog.getOpenFileName(self, 'Open File') + if file: + data_IN_FILE.clear_data() + self.file_name = file + self.Fname_label.setText(self.file_name) + my_data.get_data_bin(self.file_name, data_IN_FILE) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_FILE.data_length))) + MODE_FILE = True + + def draw_file(self): + scale = self.get_scale() + size = scale * my_data.DATA_RATE + for x in range(self.point, data_IN_FILE.data_length, 20): + self.curve10.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA[x: x + size]) + self.curve20.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_pos_sig[x: x + size]) + self.curve21.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_din_threshold[x: x + size]) + self.curve26.setData(self.counter_com_port[x: x + size], np.zeros(len(self.counter_com_port[x: x + size]))) + self.curve24.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_min_threshold[x: x + size]) + self.curve25.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_max_threshold[x: x + size]) + self.curve30.setData(self.counter_com_port[x: x + size], data_IN_FILE.Last_RR_poz_rel[x: x + size]) + app.processEvents() + time.sleep(0.05) + + def re_scale(self): + scale = self.get_scale() + if len(data_IN_FILE.counter) != 0: + position = self.horizontalScrollBar.value() + self.point = int((len(data_IN_FILE.counter) / my_data.DATA_RATE) * (position / 1000)) + + def on_scroll(self): + self.re_scale() + + def on_close(self): + """ + Закрыть открытый для чтения файл + """ + global MODE_FILE + MODE_FILE = False + # очистка графиков + self.graph_clear() + # Пересоздание графиков + self.graph_init() + # очистка модели + data_IN_FILE.clear_data() + + self.Fname_label.setText('') + + def get_scale(self): + scale = int(self.RA_scale_BOX.currentText()) + return scale + + def on_scale_change(self): + scale = self.get_scale() + data_IN_COM.data_length = scale * my_data.DATA_RATE + data_IN_COM.update_data(data_IN_COM.data_length) + + if MODE_FILE: + self.draw_file() + + all_items = self.graph_1.items() # Получаем все элементы + text_items = [item for item in all_items if isinstance(item, TextItem)] + for text_item in text_items: + self.graph_1.removeItem(text_item) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_COM.data_length))) + + def update_list(self): + """Создание списка доступных для ввода/вывода портов""" + portlist = [] # создадим пустой список в который и будем всё записывать + ports = QSerialPortInfo().availablePorts() # доступные порты для обмена данными + for port in ports: + portlist.append(port.portName()) + return portlist + + def check_work_mode_viewer(self): + """ + Выбор режима работы (чтение данных из файла/из com-порта), в случае чего некоторые элементы отображения + становятся видимыми и доступными, а другие наоборот становятся недоступными. + """ + if self.radioButton_work_mode_com_port.isChecked(): + self.OpenB.setVisible(False) + self.CloseB.setVisible(False) + self.Play.setVisible(False) + self.Pause.setVisible(False) + self.Fname_label_com_port.setVisible(True) + self.Fname_w_label.setVisible(True) + self.ComL.setVisible(True) + self.Read.setVisible(True) + self.Stop.setVisible(True) + self.Write_file.setVisible(True) + self.Close_file.setVisible(True) + self.Restart.setVisible(True) + self.horizontalScrollBar.setVisible(False) + else: + self.on_stop() + self.OpenB.setVisible(True) + self.CloseB.setVisible(True) + self.Play.setVisible(True) + self.Pause.setVisible(True) + self.Fname_label_com_port.setVisible(False) + self.Fname_w_label.setVisible(False) + self.ComL.setVisible(False) + self.Read.setVisible(False) + self.Stop.setVisible(False) + self.Write_file.setVisible(False) + self.Close_file.setVisible(False) + self.Restart.setVisible(False) + self.horizontalScrollBar.setVisible(True) + + def set_send_mode(self): + """Установка режима передачи данных (Bluetooth/433)""" + self.send_mode = int(self.Send_mode_Box.currentIndex()) + + # настройка параметров работы стимулятора с помощью программы + def set_DC_cut_Box(self): + """Настройка вывода сигнала с/без постоянной составляющей""" + param = int(self.DC_cut_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x21, param, 1, int) + + def set_Signal_Box(self): + """Настройка вывода сигнала с/без фильтром(а)""" + param = int(self.Signal_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x1E, param, 1, int) + + def set_Channel_Box(self): + """Установка активного канала передачи""" + param = int(self.Channel_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x1F, param, 1, int) + + def set_SD_card(self): + """Установка режима записи на карту памяти""" + param = int(self.SD_card_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x20, param, 1, int) + + def set_Spi_spot_set_spinBox(self): + """Установка коэффициента усиления сигнала""" + param = int(self.Spi_spot_set_spinBox.value()) + my_data.state_packet(self.send_mode, self.serial, 0x1C, param, 1, int) + + def set_Work_Mode_pacemaker(self): + """Установка текущего режима работы стимулятора(без/с стимуляцией или в режиме принудительной стимуляции)""" + param = int(self.Work_Mode_pacemaker.currentIndex()) # << 20 + my_data.state_packet(self.send_mode, self.serial, 0x05, param, 2, int) + + def set_RA_max_time_ms_BOX(self): + """Установка длительности поиска максимума""" + param = int(self.RA_max_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x0D, param, 2, int) + + def set_RA_min_sensitivity_BOX(self): + """Установка минимальной чувствительности""" + param = float(self.RA_min_sensitivity_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x01, param, 4, float) + + def set_RA_max_sensitivity_BOX(self): + """Установка максимальной чувствительности""" + param = float(self.RA_max_sensitivity_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x02, param, 4, float) + + def set_RA_square_time_ms_BOX(self): + """Установка длительности защиты от Т волны""" + param = int(self.RA_square_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x07, param, 2, int) + + def set_RA_square_coeff_BOX(self): + """Установка порога чувствительности Т волны""" + param = float(self.RA_square_coeff_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x03, param, 4, float) + + def set_RA_all_time_ms_BOX(self): + """Установка максимального размера RR-интервала""" + param = int(self.RA_all_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x09, param, 2, int) + + def set_High_Tf_spinBox(self): + """Установка длительности RR-интервала для определения события как фибрилляции""" + param = self.High_Tf_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x10, param, 2, int) + + def set_High_Tt2_spinBox(self): + """Установка длительности RR-интервала для определения события как тахикардии 2ст""" + param = self.High_Tt2_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x11, param, 2, int) + + def set_High_Tt1_spinBox(self): + """Установка длительности RR-интервала для определения события как тахикардии 1ст""" + param = self.High_Tt1_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x12, param, 2, int) + + def set_Low_V_spinBox(self): + """Установка порога корзины фибрилляции""" + param = self.Low_V_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x13, param, 1, int) + + def set_hv_step_number_spinBox(self): + """Установка количества ступеней BB - терапии""" + param = self.hv_step_number_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x14, param, 1, int) + + def set_min_energy_spinBox(self): + """Установка минимальной энергии стимулятора""" + param = self.min_energy_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x15, param, 2, int) + + def set_max_energy_spinBox(self): + """Установка максимальной энергии стимулятора""" + param = self.max_energy_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x16, param, 2, int) + + def set_hv_blind_time_spinBox(self): + """Установка времени слепоты после разряда""" + param = self.hv_blind_time_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x18, param, 2, int) + + def set_standby_timer_spinBox(self): + """Установка таймаута отключения""" + param = self.standby_timer_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x1B, param, 4, int) + + def set_redet_num_spinBox(self): + """Установка размера буффера редетекции""" + param = self.redet_num_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x19, param, 1, int) + + def set_redet_bad_spinBox(self): + """Установка порога буффера редетекции""" + param = self.redet_bad_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x1A, param, 1, int) + + def set_fibr_cnt_max(self): + """Визуализация корзины фибрилляции""" + # + отправить стимулятору + self.progressBar_f.setMaximum(self.Low_V_spinBox.value()) + + def set_tachy_2_cnt_max(self): + """Визуализация корзины тахикардии 2ст""" + self.progressBar_t2.setMaximum(self.Low_V_spinBox_3.value()) + + def set_tachy_1_cnt_max(self): + """Визуализация корзины тахикардии 1ст""" + self.progressBar_t1.setMaximum(self.Low_V_spinBox_4.value()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7448fd8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# python12 +PyQt5~=5.15.11 +pyqtgraph~=0.13.7 +numpy~=2.2.3 +