Четыре года назад у меня Хабре была статья про цифровой недокументированный формат кардиограммы, структуру которого я попытался разгадать по максимуму. В результате мне удалось увидеть формы сигналов сторонними программными инструментами, в том числе и в аудиоредакторе. Более того, я разобрался, как вычислить недостающие 4 отведения, так как в файле было записано только 8 каналов. И в целом мне повезло с форматом представления цифровых данных: никаких там ADPCM 12 бит и близко не было.

В этом году пару месяцев назад мне пришлось столкнуться ещё с одним частным форматом кардиограммы. На этот раз речь пойдёт о мобильном кардиографе белорусской компании «Кардиан». Основная его аппаратная часть – коробка с электроникой, куда подключаются через разъём электроды для снятия кардиограммы. Дополнительная часть – устройство со смартфоном на андроиде, оснащённое термопринтером. Обе части соединяются через Bluetooth. В основной части стоит современный популярный микроконтроллер STM32, но данная статья не про обратную разработку электроники.

Рис.1. Общий вид комплекса
Рис.1. Общий вид комплекса

На смартфоне с термопринтером установлена программа Cardian-PM. Можно не печатать сделанную кардиограмму, а забрать её в цифровом виде (в виде файла *.ECG). Конечная цель – разобраться в структуре этих файлов для дальнейшего их преобразования в один из популярных форматов и открытия в популярных компьютерных программах по ЭКГ.

Итак, на руках у меня было множество файлов различных кардиограмм, полученных с этого аппарата. Имя каждого файла содержит дату и время кардиограммы, когда она была сделана. Один файл – один пациент (ну это должно быть очевидно). Размер каждого файла составляет 80200 байт. Для начала я открыл несколько файлов в HEX‑редакторе. В глаза бросается 200-байтный заголовок, в котором содержатся как текстовые, так и числовые данные. Текстовые (строковые) данные практически все очевидны: ФИО и ��ата рождения пациента, дата и время кардиограммы, совпадающая с датой и временем в имени.

Рис.2. Вид файла ECG в HEX редакторе
Рис.2. Вид файла ECG в HEX редакторе

А вот с числовыми данными пришлось повозиться подольше. Для этого я написал небольшую программку, чтобы распарсить интересующие меня константы. Данная программа формирует таблицу в формате CSV, строчки которой соответствуют моим файлам, а столбцы – значениям байтов по интересующим меня смещениям. Смещения и значения полей с данными я пока что писать не буду, здесь я привожу общие рассуждения. А позже я попробую все рассуждения свести в одну таблицу. В целом же, 200-байтовый заголовок файлов прореженный, очень много нулевых байтов. Но несколько ненулевых байтов меня заинтересовали больше всего.

Рис.3.Таблица байтов заголовка множества файлов ECG
Рис.3.Таблица байтов заголовка множества файлов ECG

Благодаря полученной таблице я сразу увидел, что эти значения полностью коррелируют со значениями даты и времени кардиограммы. Они фактически совпадают с этими значениями, записанные в непривычном, но легко читаемом big-endian 16-битном формате. Я удивлён, почему я это сразу не заметил в HEX-редакторе? Наверное, потому что я уже видел в заголовке информацию о дате и времени, правда, в текстовом виде. Поэтому я сразу подсознательно решил, что информации о дате и времени в заголовке больше быть не должно, иначе была бы избыточность. Тем более, она содержится в имени файла.

Когда я открыл один из файлов ECG в программе Cardian-PM на устройстве Android, то, перейдя в так называемую карту пациента, я увидел те самые значения, которые содержались в заголовке файлов. Оказывается, что дата в текстовом виде – это значение поля «Номер» в карте пациента. Видимо, программа на этапе записи кардиограммы присваивает номер, совпадающий с датой. Ещё есть поле в карте пациента – пол. И тут же я нашёл в заголовке байт, отвечающий за кодировку пола. Поле «Возраст» рассчитывается автоматически на основе даты рождения и даты кардиограммы. Поле «АД» (артериальное давление) везде «/» (то есть, не указано, не измерено или не предусмотрено). В заголовке этому полю соответствуют байты в текстовом виде, которые я сразу тут же нашёл: есть один байт с текстовым значением «/». Все сомнительные байты я проверял перезаписью другими значениями и последующей перезагрузкой файла в устройство с повторным просмотром, убеждаясь в своих догадках и правоте. Раздел «ПАРАМЕТРЫ ЭКГ» в карте пациента – расчётные значения временных интервалов между зубцами. Они рассчитываются внутри программы просмотра и не имеют отношения к заголовку.

Рис.4. Соответствие полей карты пациента и заголовка файла
Рис.4. Соответствие полей карты пациента и заголовка файла

После заголовка, как я увидел опытным невооружённым глазом, идут подряд 16-битные числа в little-endian формате. Каждая пара соседних чисел имеет небольшую разность. Это такой же случай, как в моей предыдущей статье про кардиограммы. Долго не думая я открыл один из файлов с кардиограммой в Adobe Audition как PCM данные «16 бит little-endian моно» с произвольной частотой дискретизации. После этого я сразу же получил потрясающую картину, причём в нормальном масштабе по вертикали: 8 одинаковых по времени фрагментов сигналов кардиограммы. Такой же случай, как в моей прошлой статье: 8 отведений физических и, стало быть, 4 недостающих, вычисляющиеся программно на этапе отображения в Cardian-PM или печати. Пробелов межу каналами нет. Усреднённых периодов и диагностической информа��ии также не видно.

Рис.5. Волновой вид файла CFG в Adobe Audition, как RAW PCM данные (в двух масштабах)
Рис.5. Волновой вид файла CFG в Adobe Audition, как RAW PCM данные (в двух масштабах)

Слева в самом начале файла можно видеть кусочек мусора из 100 сэмплов – это заголовок, интерпретированный в виде волны. Длина одного канала в сэмплах составляет 5000. То есть, 8 каналов, длиной 5000 сэмплов, по 16 бит (2 байта) на сэмпл совместно с заголовком составляет 8*2*5000+200=80200 байт. А это и есть размер наших файлов, всё чётко сходится. Частоту дискретизации я подобрал опытным путём. В данном случае она составляет 500 Гц, и записи кардиограммы длятся 10 сек. Позже я это подтвердил, увидев длительности в программе Cardian-PM. О вертикальном масштабе (разрешении по амплитуде) сразу сложно было судить, и я решил разобраться с этим в последнюю очередь.

Когда я начал внимательно сравнивать формы сигналов из Adobe Audition с формами в Cardian-PM одно и того же файла, то я реально стал в тупик. Я увидел, что совпадения форм наблюдаются далеко не везде. Отведения I и II совпадают полностью, но с одной оговоркой: при расположении сигнала отведения I в файлах ECG на первом месте сигнал II расположен в самом конце! То есть, сначала идёт сигнал отведения I, затем – грудные отведения V1-V6 и, наконец, сигнал отведения II. Анализируя спектр сигнала одного из файлов, видно, что первый и последний – сигналы одного рода, а остальные, внутренние сигналы – другого.

Рис.6. Спектральный вид файла CFG в Adobe Audition
Рис.6. Спектральный вид файла CFG в Adobe Audition

Сигналы грудных отведений совпадают не полностью. Более того, анализируя их более внимательно, я сделал предварительный вывод, что в файлах ECG они расположены задом наперёд: V6…V1, со второго по предпоследнее место. Так вот, сигналы V1-V6 сходятся, но не полностью, в отличие от I и II.

Чтобы понять эту хитрую арифметику сигналов, я прибегнул к трюку с подменой сигналов в файле ECG на свои какие-либо синтетические сигналы. Предварительно я их сформировал в Excel и построил их графики, а затем написал программу для их генерации по тем же форм��лам. В канал 1 (отведение I) я поместил сигнал синуса высокой частоты, в каналы с 2 по 7 («почти» грудные отведения V6…V1) – сигналы-графики полуокружности, параболы, модуля линейной функции и их инверсии. В последний канал 8 (отведение II) я поместил сигнал синуса низкой частоты. В документе Excel я вычислил недостающие 4 отведения по известным формулам, обкатанные в предыдущей статье, и также изобразил их на графике (на рисунке выделены в красную рамку). Сформированный файл ECG с «левыми» сигналами я увидел Adobe Audition. Получилось красивее, чем я ожидал.

Рис.7. Синтетические сигналы в MS Excel и Adobe Audition
Рис.7. Синтетические сигналы в MS Excel и Adobe Audition

И вот, настал момент открыть этот файл в Cardian-PM. Как и ожидалось, кроме двух синусов (I и II) я не увидел свои формы в чистом виде! Они все «модулированы» отведениями I и II. А порядок расположения сигналов грудных отведений в файле, который я ранее объявил обратным, подтвердился. Кстати, виды форм сигналов четырёх вычисленных отведений (III и усиленные aVL, aVR, aVF) совпали с видами в Excel, что уже радует.

Рис.8. Интерпретация синтетических сигналов в Cardian-PM (в двух масштабах по вертикали)
Рис.8. Интерпретация синтетических сигналов в Cardian-PM (в двух масштабах по вертикали)

Опытным путём в сумме двух сигналов можно видеть, какой сигнал и с каким коэффициентом (1, 1/2 или меньше) и знаком задействован в этой сумме. Возможно, я не совсем удачно выбрал сигнал с синусом низкой частоты в качестве отведения II. Это не даёт в полной мере оценить вклад этого сигнала в формирование видов грудных отведений в сформированном мной файле. Поэтому пришлось немного изменить эксперимент: каналы с 2 по 7 я полностью обнулил прямо в Adobe Audition (заменил тишиной) и пересохранил файл. Эти изменённые эксперименты я не фотографировал в подробностях, так как было не до этого. После повторного открытия в Cardian-PM я увидел на месте грудных отведений 6 одинаковых комбинаций форм отведений I и II. Эта комбинация как раз и является неизвестной добавкой, которую надо учесть, чтобы получить виды сигналов грудных отведений. Точнее, чтобы получить виды грудных отведений (что нужно на практике), нужно вычесть эту комбинацию из сигналов грудных отведений (которые в каналах в файле). Тут можно совсем запутаться, особенно, когда эта комбинация оказывается с противоположным знаком.

Рис.9. То же самое, но с тишиной в сигналах грудных отведений
Рис.9. То же самое, но с тишиной в сигналах грудных отведений

Чтобы уточнить коэффициенты сигналов I и II, внёсшие вклад в грудные отведения, я прибег к сигналам функций меандра и константы, в частности, нуля. Их я помещал на место сигналов I и II в различных комбинациях. Оба коэффициента я оценил примерно как 1/3. Заодно, благодаря известной амплитуде моих сигналов, я произвёл довольно точную оценку разрешающей способности по амплитуде, обращая внимание на клетки и на масштаб в мм/мВ в Cardian-PM. Она составила 12 мВ. То есть, от -6 до 6 мВ – полный размах сигнала. Меандр с размахом 65536/12=5461 будет отображаться в Cardian-PM высотой в одну 5-мм клетку при выборе масштабе 5 мм/мВ.

Рис.10. Меандр и ноль на месте сигналов I и II отведений (и наоборот)
Рис.10. Меандр и ноль на месте сигналов I и II отведений (и наоборот)

А что же всё-таки за комбинации из отведений I и II в грудных отведениях? Мне пришлось прибегнуть к изучению матчасти, чтобы ответить на этот вопрос. Как выяснилось, это сумма отведений I и II с множителем 1/3, как я и предполагал до этого. Его называют терминалом WCT – такой себе «виртуальный ноль». Не буду вдаваться в математические подробности и тонкости, а опишу более-менее абстрактно, не совсем точно, но логично в рамках нашего случая с арифметикой сигналов. Итак, вот что мы имеем. Эмпирическим путём я установил, что требуемые сигналы грудных отведений от сигналов в соответствующих им каналах файла ECG отличаются на сигнал 1/3*(I+II). Примем это за факт. И я назвал это выражение сигналом WCT. Неправильно говорить «сигнал WCT», но я так образно выразился. Для условности представления вычислений в векторном виде я взял «правую ногу» за ноль, хотя на самом деле это не более чем абстракция. Сигнал WCT, как я его назвал, на самом деле рассчитывается как 1/3*(I+II) плюс потенциал правой руки. Этот факт я нашёл в интернете.  Но первое слагаемое 1/3*(I+II) это «поправка» в сигналах грудных отведений. Отсюда следует, что сигналы каналов грудных отведений записаны относительно правой руки, но не правой ноги, как я ошибочно предполагал до этого. Учитывая специфику отведений для I и II, напрашивается вывод, что правая рука является общей точкой для всех 8-ми каналов в файлах ECG.

Я изобразил два рисунка. Треугольники прямоугольные, так как мне они больше нравятся. Хотя чаще всего можно встретить равносторонний перевёрнутый треугольник. На первом – векторы сигналов в каналах файлов ECG. На втором – реальные сигналы 12 отведений, которые изображаются на кардиограмме. Множитель 1/3 берётся исходя из тех же геометрических соображений: точка WCT является центром пересечения медиан треугольника, и она делит их в соотношении 1:2. Медианы – это отведения aVR, aVL и aVF. Именно медианы, идущие от середины стороны к углу треугольника, а не от точки WCT, вопреки некоторым неправильным иллюстрациям в интернете. А если их брать от точки WCT – это были бы VR, VL и VF, которые в полтора раза меньше усиленных. И, кстати говоря, сигнал VR равен той самой комбинации 1/3*(I+II), но взятой с противоположным знаком. Я рисую сигналы отведений векторами (стрелочками) чтобы понимать полярность сигнала. Это же разность двух потенциалов, но какой именно потенциал в качестве «уменьшаемого», а какой в качестве «вычитаемого» – это показывает вектор. Обычно из конца вычитаем начало. Красным цветом изображены отведения I, II, III. Зелёным – aVR, aVL, aVF. Синим – V1...V8.

Рис.11. Векторные диаграммы каналов записи и отведений на распечатке
Рис.11. Векторные диаграммы каналов записи и отведений на распечатке

В итоге я составил более-менее понятную таблицу с кратким описанием смещений и полей файла ECG. Все подробности излагались выше на протяжении всей статьи.

Рис.12. Структура файла ECG, которую я распознал
Рис.12. Структура файла ECG, которую я распознал

В следующей части я попробую написать программу для преобразования этих файлов в популярный формат EDF с открытой спецификацией.

Всех с наступающим новым годом!