====== Contec ECG90A Electrocardiograph - ECG File Format ======
This is my approach in the **reverse engineering** of **ECG file format** saved by the **[[http://www.contecmed.com/index.php?option=com_virtuemart&page=shop.product_details&flypage=flypage.tpl&category_id=11&product_id=97&Itemid=588|ECG90A]]** Electrocardiograph device, produced by **[[http://www.contecmed.com/|Contec]]**. The device can store an ECG case into the **microSD card**. During the ECG session, just choose the **Store** option as the print format, and then press the **Start** button.
Here you can find a **Python program** to draw PDF or PNG electrocardiograms from ECG files: **[[https://github.com/RigacciOrg/ecg-contec|ecg-contec GitHub repository]]**.
Here you can find a **review** (in Italian) of the device: **[[ecg_contec_90a]]**.
===== File Header =====
Each file has the first **43 bytes** allocated as an header to store some metadata:
^ Field name ^ Size ^ Note ^
^ Case name | 8 bytes | Example: ''0000011'', null terminated. |
^ Unknown | 2 bytes | Seems to be always two null chars. |
^ Timestamp | 20 bytes | Example: ''2020-11-12 18:09:05'', null terminated. |
^ Unknown | 2 bytes | Seems to be always two null chars. |
^ Name | 8 bytes | Name of the patient, null terminated. |
^ Sex | 1 byte | 0 = F, 1 = M, 255 = Blank |
^ Age | 1 byte | Cannot enter a number greather than 200. Zero if missing. |
^ Weight | 1 byte | Zero if missing. |
===== Payload =====
Just after the header, the file contains the data payload. The device stores **800 samples per second**, i.e. data is acquired at **800 Hz** rate. Each sample consists of **eight** (beware: 8 values, not 12!) **16-bit unsigned integers**, in little-endian order.
This means that only **eigth series of data** are stored. This is a bit surprising because the device is presented as capable of running the standard **[[wp>Electrocardiography|12-lead ECG]]** and it can print all the 12 graphs. In the following table are listed the standard ECG 12 leads, the ones stored into the file have the label in gray:
| **I** | Bipolar | Limb lead between the right arm and the left arm (**red** and **yellow** electrodes). |
^ II | Bipolar | Limb lead between the right arm and the left leg (**red** and **green** electrodes). |
^ III | Bipolar | Limb lead between the left arm and the left leg (**yellow** and **green** electrodes). |
| **avR** | Unipolar | Augmented voltage right, limb lead, **red** electrode. |
| **avL** | Unipolar | Augmented voltage left, limb lead, **yellow** electrode. |
| **avF** | Unipolar | Augmented voltage foot, limb lead, **green** electrode. |
^ V1 | Unipolar | Precordial lead (chest) **V1** precordialprecordialwhite or red. |
^ V2 | Unipolar | Precordial lead (chest) **V2** white or yellow. |
^ V3 | Unipolar | Precordial lead (chest) **V3** white or green. |
^ V4 | Unipolar | Precordial lead (chest) **V4** white or brown. |
^ V5 | Unipolar | Precordial lead (chest) **V5** white or black. |
^ V6 | Unipolar | Precordial lead (chest) **V6** white precordialor violet. |
My guess (to be confirmed) is that of the six **limb** leads, **only the II and the III are actually stored** and the remaining six data series, are the **precordial** ones. This means that the missing values (leads **I**, **avR**, **avL** and **avF**), are derived by some mathematical formulas.
My guess is supported by some tests I made: I copied an hand-crafted ECG file into the SD card of the ECG90A, where the values of the first two series were replaced by some constant values. If the file is viewed (replayed) on the device, all the first six series show as stright lines. If only the first serie is replaced with a constant value, the II lead is a stright line, if I replace the secon serie with a constant, only the III lead is a stright line.
So, my guess is that each sample is made up of **8 lead values**, each of which is a **16-bit unsigned integer** in little-endian order, for a total of **16 bytes**:
^ II ^ III ^ V1 ^ V2 ^ V3 ^ V4 ^ V5 ^ V6 ^
If the device cannot measure a value (e.g. if an electrode is disconnected), you will find the hex value **0x6800** into the file.
Our tests show that **the values**, under normal conditions, **vary in a range** that goes approximately from a minimum of **1700** to a maximum of **2200**. This means that the actual resolution of the analog to digital converter is far below the 16 bits allocated for each sample; it can be assesed instead to about a **10 bit** value.
The **unit of measure** of the values seems to be **0.005 mV**. This results from empirical measure of a variation of 200 units over the ECG plot, which resulted into a variation of 10 mm using a plot scale of 10 mm/mV.
It seems also that the values should be **shifted** by **-2048** to obtain a zero-centered graph.
===== Footer =====
After the data payload, the file is closed by **37 bytes**. Generally they are **all zeros bytes**, but in some cases I have found a different value at position 26 (counting from 0): **0x0116** or **0x0016**. The different word is present in cases which does not have any obvious difference from the others.
So it seems that the end of data can be detected by **an entire row of zeros** (16 bytes), or just skipping the last 37 bytes of the file.
===== Electrodes connection detection =====
- **Detection of connected electrodes**. The device requires that both the **right arm red** (RA) and **right leg black** elecrodes are connected to acknowledge that the red electrode is in place (the flashing RA red label disappear). However no ECG graph is traced unitll all the four limb leads are connected.
- Actually the **ECG90A requires that all the four limb electrodes are connected** to start acquiring something. In theory: to acquire **lead II** the **right arm** and the **left leg** electrodes should be sufficient; to acquire **lead III** the **left arm** and the **left leg** electrodes should be sufficient. This is because they are bipolar leads into the Einthoven triangle.
- Also leads **I**, **avL**, **avR** and **avF** are plotted only when all the four limb electrodes are in place.
- Leads **V1**, ..., **V6** starts acquiring with just the relative chest electrode and the right leg one (black).
===== Calculating the missing leads =====
Here are the **formulas to calculate the values for leads** not included into the data series.
From the formulas of voltages of **[[wp>Electrocardiography#Limb_leads|Limb_leads]]**, we have:
I = LA - RA
II = LL - RA
III = LL - LA
This should mean that **lead I** can be calculated by:
I = II - III
The following formulas to calculate the unipolar limb leads are taken from the **Goldberger’s Lead System**, well explained in this article: **[[https://www.cardiosecur.com/magazine/specialist-articles-on-the-heart/lead-systems-how-an-ecg-works|ECG Lead Systems]]**. See also the [[https://www.physionet.org/physiotools/wag/parses-1.htm|parsescp manual page]]. The formulas seems to be confirmed by the **empirical observation of the graphs** of the hand-crafted test file.
avR = 1/2 * III - II
avL = 1/2 * II - III
avF = 1/2 * (II + III)
===== Sample files and other formats =====
=== ECG ===
**{{.:ecg90a:0000037.ecg.tar.gz|0000037.ECG}}** - This is a file downloaeded from the **Contec ECG90A**. It is a 10 seconds recording, where only the limb leads were attached; so only the first two data series (**lead II** and **lead III**) contain valid numbers. Beside the original file, the archive contains a file called **0000037.ECG.csv** containing the same data converted in CSV format. The CSV contains all the six limb leads data, four of them are calculated with the formulas above. Finally the archive contains a file called **0000037.ECG.edf** with the CSV data converted using **[[https://www.teuniz.net/edfbrowser/|EDFbrowser]]** (with the included **ecg90a.template**), to be viewed in the EDFbrowser program itself.
This **ECG proprietary format** is explained above: it is used by the **Contec ECG90A** and may be by other devices from the same manufacturer.
=== EDF ===
The **[[https://www.edfplus.info/index.html|European Data Format (EDF)]]** is a simple and flexible format for exchange and storage of multichannel biological and physical signals. It is an open format, here you can find an **[[https://www.teuniz.net/edfbrowser/edf%20format%20description.html|explained example]]**. The open source program **[[https://www.teuniz.net/edfbrowser/|EDFbrowser]]** is a viewer for that format. A limitation of this format is that it is not specifically targeted to ECG, so the existing viewers generally does not have specific functions required to view an ECG.
=== SCP-ECG ===
The **[[wp>SCP-ECG]]** (Standard Communications Protocol for Computer-Assisted Electrocardiography) is the European standard for communication of resting ECGs. SCP-ECG (as per the accepted **[[https://www.iso.org/standard/46493.html|ISO standard 11073-91064:2009]])** is widely accepted even if limited to short term resting ECG. Free documentation about the format is scarce, even the historical **[[https://webstore.ansi.org/standards/aami/ansiaamiec712001|ANSI/AAMI EC71:2001]]** is available only at astronomical price.
The format is rather complicated; it can contain raw or **Huffman compressed** data, it allows custom and multiple Huffman tables, data sequences can be actual values or first or **second differences**, can store **QRS** and **rhythm data**, rhythm data can be stored as plain values or as **reference beat subtraction**, bimodal compression can be used too. In the face of all these complications, **only 65535 bytes** can be stored for each data set, which means that this format is **not suitable** for recordings lasting **more than thirty seconds**!
Starting with version V3.0 (year 2014), the standard also provides support for the storage of continuous, **long-term ECG** recordings.
=== HL7 aECG ===
The **[[wp>HL7 aECG]]** (the HL7 Annotated Electrocardiogram) is a standard created in response to a Food and Drug Administration’s digital electrocardiogram initiative. It was accepted by ANSI May, 2004.
=== DICOM ===
**[[wp>DICOM|Digital Imaging and Communications in Medicine (DICOM)]]** - Since the year 2000 the widely used DICOM standard has included rules for diagnostic ECG waveforms, but for a long time, no ECG manufacturer had marketed electrocardiographs that support the DICOM waveform standard. It was spring 2006 before the first ECG manufacturer announced its adoption of the DICOM standard for diagnostic electrocardiographs. %%[%%[[#web_references|4]]%%]%%. The only drawback of DICOM is the complexity of the standard that requires a developer to have a prior knowledge of DICOM philosophy %%[%%[[#web_references|2]]%%]%%.
To confirm the **insane complexity** of the standard, it is sufficient to say that it supports three different types of ECGs:
**12-Lead ECG** for shor term measures (1 to 13 channels, 200 to 1000 Hz, max 16384 samples), **General ECG** (1 to 24 channels, 200 to 1000 Hz), **Ambulatory ECG** for long term measures, e.g. Holter, etc (1 to 12 channels, 50 to 1000 Hz).
===== Web References =====
- **{{.:ecg90a:ecg-toolkit-dicom-open-source.pdf|An Open Source ECG Toolkit with DICOM}}**
- **{{.:ecg90a:gateway-scpecg-dicom.pdf|A Gateway between the SCP-ECG and the DICOM Standard}}**
- **{{.:ecg90a:dicom-ecg-supplement.pdf|DICOM Supplement 30: Waveform Interchange}}**
- **{{.:ecg90a:advantage-of-dicom-ecg-standard.pdf|Innovation and advantage of the DICOM ECG standard...}}**
- **{{.:ecg90a:spc-ecg-v30-standard.pdf|SCP-ECG V3.0: An Enhanced Standard Communication Protocol for Computer-assisted Electrocardiography}}**
- **{{.:ecg90a:scp-ecg-is-format-suitable.pdf|Is the SCP-ECG Format Suitable for Inpatient Ecg Management?}}**
* **[[https://stackoverflow.com/questions/40121146/how-to-write-a-dicom-file-from-raw-ecg-data|How to write a DICOM file from raw ecg data]]**
* **[[https://sourceforge.net/projects/ecgtoolkit-cs/|C# ECG Toolkit]]** ECG Toolkit support for: SCP-ECG, DICOM, HL7 aECG, ISHNE & MUSE-XML
* **[[https://pypi.org/project/dicom-ecg-plot/|Python Dicom ECG plot]]**
* **[[https://ecg.galliera.it/|DICOM ECG Viewer on-line]]** based on the Dicom ECG plot software
* **[[https://www.edfplus.info/specs/edf.html|Full specification of EDF]]**
* **[[http://ems12lead.com/2014/03/10/understanding-ecg-filtering/|Understanding ECG Filtering]]**
* **[[https://stackoverflow.com/questions/25191620/creating-lowpass-filter-in-scipy-understanding-methods-and-units|Creating lowpass filter in SciPy - understanding methods and units]]**
* **[[https://stackoverflow.com/questions/13740348/how-to-apply-a-filter-to-a-signal-in-python/13740532|How To apply a filter to a signal in python]]**
* **[[https://dsp.stackexchange.com/questions/49460/apply-low-pass-butterworth-filter-in-python|Apply Low pass Butterworth filter in Python]]**
* **[[https://medium.com/analytics-vidhya/how-to-filter-noise-with-a-low-pass-filter-python-885223e5e9b7|How to filter noise with a low pass filter — Python]]**
* **[[https://medium.com/swlh/noise-removal-for-a-better-fast-fourier-transformation-284918d4250f|Noise Removal For A Better Fast Fourier Transformation]]**
* **[[https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirnotch.html|Design second-order IIR notch digital filter]]**
* **Debian GNU/Linux Software**
* **[[https://packages.debian.org/buster/sigviewer|sigviewer]]** GUI viewer for biosignals such as EEG, EMG, and ECG
* **[[https://packages.debian.org/buster/edfbrowser|edfbrowser]]** viewer for biosignal storage files such as bdf and edf
* **[[https://packages.debian.org/buster/biosig-tools|biosig-tools]]** format conversion tools for biomedical data formats
* **[[https://github.com/gitrust/scpinfo|scpinfo]]** Python library stub to read SCP-ECG files (with example.scp)
* **MS-Windows Software**
* **[[https://listoffreeware.com/free-ecg-viewer-software-windows/|6 Best Free ECG Viewer Software For Windows]]**
* **[[https://www.teuniz.net/edfbrowser/]]**
* **[[http://www.ecg-soft.com/ecgviewer/ecgviewer.htm]]**
* **Python Libraries**
* **[[https://medium.com/analytics-vidhya/how-to-filter-noise-with-a-low-pass-filter-python-885223e5e9b7|How to filter noise with a low pass filter — Python]]**
* **[[https://dsp.stackexchange.com/questions/49460/apply-low-pass-butterworth-filter-in-python|Apply Low pass Butterworth filter in Python]]**
* **[[https://stackoverflow.com/questions/25191620/creating-lowpass-filter-in-scipy-understanding-methods-and-units|Creating lowpass filter in SciPy - understanding methods and units]]**
* **[[https://dsp.stackexchange.com/questions/19084/applying-filter-in-scipy-signal-use-lfilter-or-filtfilt|Applying filter in scipy.signal: Use lfilter or filtfilt?]]**
* **[[https://github.com/dy1901/ecg_plot|ECG plot]]**