Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Easy Analog Data Compression


Easy Analog Data Compression

A low-footprint compression technique for when efficiency really matters. When I needed to implement a lossless data compressor for 16-bit ECGs (electrocardiograms), EEGs (electroencephalograms), and other medical signals in C on a 16-bit integer processor, I discovered how little value the majority of compression techniques were for my problem. Nearly all of the techniques I found on the Internet and in computer science literature rely on two fundamental techniques. The first technique consists of repetitive patterns (known as the Lempel-Ziv family of algorithms, such as gzip) that can be encoded in an efficient way. The second technique uses a family of approaches (known as entropy coding or Huffman coding) that exploit the fact that the individual numbers/letters found in the raw data are usually not equally probable. Both techniques make use of look-up tables -- the larger the tables, the better the chance for good compression ratios. That was another challenge in my project. My algorithm should use less than 2KB of data memory for up to four independent channels and should not need more than 1,000 instruction words. I needed something very simple but effective.

To the untrained human eye, an ECG appears to be extremely repetitive (see Figure 1). Each heartbeat yields a waveform that looks very much like the preceding heartbeat, but the data values differ slightly. The barely visible differences of the high-resolution signals make the compression with standard techniques nearly impossible. In an experiment, I assessed that a standard version of gzip reduces the size of my ECG by barely five percent although I consider myself quite healthy. An EEG curve (see Figure 2) is so irregular that zip, pack, and others do an even worse job.

However, I know something that Lempel, Ziv, and Huffman do not. My data consists of digitized signals originating from an analog source. These signals are known to exhibit relatively few big jumps in their signal amplitude. For signals that change slightly over time, "delta encoding" (i.e., the coding of the signed differences between two sample values instead of the sample values themselves), saves a lot of memory. Suppose, for example, you know that the absolute difference between two adjacent samples of the 16-bit signal never exceeds 1,023; you can code the signal as one absolute value of 16 bits followed by the delta values of 11 bits each.

Adaptive Delta Encoding

The problem with the signals shown in Figures 1 and 2 is that the delta values (i.e., the differences between two adjacent samples) vary comparatively slowly, but vary a lot. Sometimes as few as 4 or 5 bits suffice to code the delta values; sometimes 12 to 14 bits are needed. In order to increase compression power, I made the bit width used by the delta code adaptive. The encoder algorithm detects when the delta values need fewer bits than the current bit width of the delta code. If that is the case, it reduces the number of bits used to code the delta values. In my program, I do this by checking if the two most significant bits in each of the last BIT_REDUCTION_LENGTH-produced codes are identical (see function storeDelta16 in Listing 1). If this is the case, the bit width of the delta code, iBitWidthOfCode, is decreased by one. The decoder does exactly the same, and thus it is not necessary to mark the place where iBitWidthOfCode is reduced.

If a new delta value cannot be represented by the current bit width, then iBitWidthOfCode must be increased. This time the decoder cannot anticipate the change and must be informed. The encoder does this by storing a reserved letter piOVFLCODE[] that exists for every possible delta bit width. When the decoder encounters such a reserved letter, it knows it must increase its iBitWidthOfCode by one. To reduce excessive coding of piOVFLCODE[] when iBitWidthOfCode gets so small that merely signal noise is coded, I have introduced the lower bound MIN_BIT_WIDTH.

I also have reserved two other letters: piALIGNCODE[] indicates that the next code is aligned with the next word boundary, an indication I need to implement flushing; piABSCODE[] indicates that an absolute code (a raw sample value) follows, aligned with word boundaries.

These reserved letters are chosen to flank the largest and smallest possible two's complement numbers of the delta code. If a delta value matches a reserved letter, the bit width must be increased before the value can be encoded without ambiguities. Hence, I use the reserved letters in storeDelta16 to check if iBitWidthOfCode must be increased. If this is the case, then the appropriate piOVFLCODE[] is stored, and storeDelta16() is called recursively with an increased bit width.

The function storeX, called in storeDelta16, concatenates the codes of variable bit lengths that are passed as parameters. As soon as a WORD (16 bits) is complete, it passes it to writeCode. Any code fraction that is cut off when completing a WORD is kept in the buffer iXcode. The function writeCode represents the interface of a device driver, which is supposed to accept 16-bit-entities only. In my application, data needs to be transmitted via a serial interface and written to flash memory. An additional requirement is to be able to flush the buffer iXcode at any time. This is possible by calling flushX, which checks if there is anything buffered in iXcode. If this is the case, it will add the aforementioned piALIGNCODE[] to the output and pass the content of iXcode to writeCode, although not all of its 16 bits are meaningful. When the decoding algorithm reads the reserved letter piALIGNCODE[], it knows that any undecoded remaining bits of the WORD at hand can be dismissed, and the next delta code will be found in the next WORD.

Prediction

The functions I have described so far code the delta values with an adaptive bit width, but do not yet compute the values themselves. The function encoder_storeADEPT does this. In the simplest form, this function would just compute the difference of the sample passed as parameter and the sample received previously and use this difference as parameter for a call to storeDelta16. This already yields nice compression ratios for many signal types.

However, if you can extrapolate the signal curve and predict the new sample value with reasonable accuracy, then you may encode the difference between the prediction and the actual sample value to further reduce the size of the delta code. The function encoder_storeADEPT shown in Listing 1 performs these compression steps.

I call the compression technique ADEPT (adaptive delta encoding with prediction). The prediction step itself may be chosen according to a model of the signal, heuristics, similarities to other signals recorded simultaneously, etc. Signal prediction is a complicated matter that could fill entire books or at least book chapters [1]. I decided to implement a simple linear extrapolator instead of spending the rest of my days trying to understand those books. The predictor given here records the history of the last three samples, tries to fit a line through them, and extrapolates linearly.

As you can see in encoder_storeADEPT, I have arranged the computations of the predictor such that first I do the division and then the multiplication. This makes an integer overflow less likely in case you rewrite the function to deal with 32-bit data. If the prediction fails completely, it may happen that the generated delta code cannot be represented as a 16-bit number anymore. Then the algorithm resets the predictor, writes the reserved letter piABSCODE[], and finally writes the sample directly, word aligned, without delta encoding. If no absolute code is needed, encoder_storeADEPT passes the delta value to storeDelta16.

Note that the simple predictor presented above assumes a rather continuous signal. It improves the compression for EEG signals or breathing curves, but performs very poorly for ECGs. Having no prediction step at all is sometimes better than having an inappropriate one: heartbeats of a very high amplitude may irritate the presented predictor so much that pure adaptive delta encoding outperforms adaptive delta encoding with prediction.

Decompression

Since I needed to run the ADEPT decompressor on a PC, I opted to implement it in C++. The class TDecompressor shown in Listing 2 resembles the compressor. The constructor takes a reference to a vector as an argument that holds the decompressed values. The decompressor is fed by passing the codes as parameters to the member function Decode in the order they are received or read. This function checks first if the code passed previously indicates that an absolute code would follow. In this case, the value of miAbsValueCoded would be one, and the passed code may be directly appended to the vector of decompressed values. In the more probable case, the passed code must be concatenated with the remaining fractions of the last code and split into the respective delta codes to be able to reconstruct the input values of the encoder.

Decode does this division into delta values in a while loop. As a bitmask for this job, it uses mpiABSCODE[]. The obtained delta code iSubword might represent a negative number, but it might be a positive 32-bit two's complement number and therefore must be sign extended. Think of the number 0xFF. In an 8-bit two's complement representation, it is interpreted as -1. It must be negative because the MSB (most significant bit) is 1. In a 32-bit system, this is interpreted as the positive number 255. Its MSB is 0. To interpret the number correctly in the 32-bit system, the bits to the left of the sign bit of the small-scale representation must hold a copy of this sign bit. In our example, 0xFF will be converted to 0xFFFFFFFF, which is interpreted as -1. This technique is called sign extension.

After the sign extension of iSubword, Decode checks if the obtained code is a reserved letter. If so, it changes the object's state to be able to react appropriately; else it reconstructs the original data by calling the member function DeltaDecode and stores the result in the vector named by the constructor.

Listing 3 shows an example program that uses ADEPT compression and decompression.

Performance and Application

In general, ADEPT may be used for any kind of data. However, due to its nature, it will perform badly on sample data from a very irregular signal with many jumps and big delta values. A worst-case scenario is a random signal that covers the full 16-bit range. The predictor is hopelessly lost and many absolute values must be coded; the encoded data is about 130% of the size of the raw data.

ADEPT performs well if the difference between two subsequent samples is small and/or the signal form is fairly linear. In the ideal case (i.e., when a strictly linear signal is compressed), ADEPT exhibits a compression ratio of 16:MIN_BIT_WIDTH. With a reasonable value for the minimal bit width, it is obvious that the best compression ratio that can be achieved with ADEPT is between 16:3 and 16:4, or 18 and 25percent, respectively.

Obviously a noise-free, repetitive signal can be more compressed by zip and pack than by ADEPT, which cannot go beyond the mentioned threshold. However, signals coming from natural sources are rarely exactly repetitive, therefore the aforementioned bad performance of gzip on an ECG. However, a comparison of ADEPT to gzip is unfair. Each of the algorithms has a very different application domain. Hence the attempt to compress an EEG with gzip must fail, and you should compare ADEPT to other techniques.

The strengths of ADEPT are most of all simplicity, no need for floating-point arithmetic, small memory requirements, and zero delay. The latter needs explanation: compression techniques based on so-called FIR filters inherently introduce a time delay. This may be very annoying when real-time performance is desired.

An obvious weakness of the technique is its dependency on scale (i.e., on the gain factor of the analog/digital converter); a higher gain value will lead to larger delta values for the digitized data and thus needs a wider code. Simplicity pays its price when you compare ADEPT to specialized algorithms, as shown in Table 1. Note that such comparisons are very difficult, because they are hardly ever objective. This is especially true when different input data is used to assess the performance of the respective methods, which is the case here.

Changes of the signal scale may increase or decrease the compression ratio of ADEPT given in Table 1 by about five percent. Most likely, the same is true for the two reference ECG compressors, but no figures were given in [2]. The EEG compressor in [3] is probably the most powerful known, but it is so specialized that it cannot encode arbitrary signals without losses.

A nice feature of the algorithms ALZ77 and ADPCM Subband Coding is a smooth transition from lossless to lossy compression, a step that is difficult with DPCM, ADEPT, or entropy coding. When tuning ADEPT, you should note the comparatively little compression gain when improving the prediction because of the logarithmic dependency between the delta code width and the prediction error.

Conclusion

The ADEPT algorithm is a variant of the Differential Pulse Code Modulation [4]. When comparing ADEPT to state-of-the-art ECG and EEG compressors, you will find that its compression power comes close to their levels. Its advantages are versatility, easy implementation on any integer processor, speed, and a very small memory footprint. The downloadable source code (available at <www.cuj.com/code>) provides you with an easy-to-use tool that allows you to efficiently compress digitized data from continuous signals on virtually any hardware for which a C compiler is available.

Further Reading

[1] John G. Proakis and Dimitris G. Manolakis. Digital Signal Processing: Principles, Algorithms, and Applications, 3rd Edition (Prentice Hall, 1996), Chapter 11.

[2] R. Nigel Horspool and Warren J. Windels. "An LZ Approach to ECG Compression," Proceedings of the 1994 IEEE 7th Symposium on Computer-Based Medical Systems. See also <www.csr.uvic.ca/~nigelh/Publications/ECG-compression.pdf>.

[3] Zlatko Sijercic, Gyan Agarwal, and Charles Anderson. "EEG Signal Compression with ADPCM Subband Coding," Proceedings of the 39th Midwest Symposium on Circuits and Systems, Aug 1996. See also <www.cs.colostate.edu/~anderson/res/eeg/>.

[4] John G. Proakis. Digital Communications, 3rd Edition (McGraw-Hill, 1996), Chapter 3.

About the Author

Stephan Grünfelder studied computer science at the Vienna University of Technology and has been developing software for embedded systems for seven years. His interests include signal & image processing, rock climbing, skiing, and sheep farming. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.