Part I: Foundations of Sensory AI
Chapter 3: Signals, Sampling, Time, and Synchronization

Quantization and compression

"Sampling decides when I get to look. Quantization decides how many answers the sensor is allowed to give. Compression decides how many of those answers survive the trip to me. I inherit whatever is left."

A Pragmatic AI Agent

The big picture

Section 3.1 handled the time axis: how fast to sample before aliasing corrupts the signal. This section handles the amplitude axis and the storage axis. A physical quantity is a continuum, but an analog-to-digital converter can only report one of finitely many levels, and a link to the cloud can only carry finitely many bits per second. Quantization is the rounding that turns a voltage into an integer; compression is what you do afterward to make those integers cheaper to store and send. Both are lossy budgets you spend on purpose, and both leave fingerprints in the data that a downstream model will happily learn as if they were real. Getting the budget wrong is how a 24-bit accelerometer ends up carrying 11 bits of actual information, or how a lossy codec invents a feature your classifier keys on.

A 24-bit accelerometer that reports only eleven honest bits is not defective. The other thirteen bits are amplifier hiss dressed up as data, and a downstream model will study that hiss as devotedly as the motion it was built to measure. This section therefore builds the amplitude story before the storage story. It leans throughout on the sampling picture from Section 3.1, the noise vocabulary of Chapter 2, and the information content that Chapter 4 makes precise. The two-stage path every sample takes, from continuous quantity to quantizer to compressed bitstream, is drawn in the figure below.

The quantize-then-compress sensor pipeline Continuous quantity v(t) a physical continuum Quantizer (ADC) round to one of 2^N rungs, step width Δ = V_FS / 2^N Integer samples amplitude fixed, error ±Δ/2 Compressor lossless (exact) or lossy (bounded distortion) bitstream to radio / storage
The two lossy-on-purpose budgets of this section in sequence: the quantizer converts a continuous quantity v(t) into integer samples on a ladder of 2^N rungs (spacing Δ), and the compressor then re-encodes that integer stream, either losslessly or lossily, into the bitstream that leaves the device. Amplitude fidelity is decided at the quantizer; transport cost is decided at the compressor.

Amplitude quantization: a continuum forced onto a ladder

Get this ladder wrong and the damage is silent: a temperature logger with too-coarse steps will flatten a slow fever curve into a staircase and a fault-detection model will read those false plateaus as the physiology, learning an artifact of the converter instead of the patient. Everything downstream inherits the rungs you pick here. What it is. An \(N\)-bit converter with full-scale range \(V_{\text{FS}}\) partitions that range into \(2^N\) equal steps. Each step has width \(\Delta = V_{\text{FS}} / 2^N\), the value of the least significant bit (LSB). A true voltage \(v\) is reported as the nearest rung of this ladder, so the reading carries an error \(e = \hat v - v\) bounded by \(\pm \Delta/2\). Twelve bits over a \(\pm 2g\) accelerometer range give \(\Delta \approx 4g / 4096 \approx 0.001g\) per step: that is the finest distinction the sensor can express, no matter how quiet its analog front end.

Why it behaves like noise. For a signal that is busy relative to \(\Delta\) (it crosses many levels between samples), the rounding error \(e\) is well modeled as uniform on \([-\Delta/2, \Delta/2]\), independent from sample to sample. A uniform distribution of width \(\Delta\) has variance \(\Delta^2/12\), so quantization injects an effective noise power of \(\sigma_q^2 = \Delta^2/12\). This is the "quantization noise" that sets a hard floor: you cannot filter below it, because it was created at the moment of rounding and carries no structure to exploit.

Checkpoint

So far: rounding a continuous value to the nearest of \(2^N\) rungs (spacing \(\Delta\)) caps the error at \(\pm\Delta/2\), and for a busy signal that error behaves like uniform noise of power \(\Delta^2/12\), a floor no later filter can remove.

How much resolution you actually buy. Comparing full-scale signal power to that noise floor gives the classic signal-to-quantization-noise ratio for a full-scale sinusoid:

$$\text{SQNR}_{\text{dB}} \approx 6.02\,N + 1.76.$$

Every added bit buys about 6 dB of headroom, roughly one extra factor of two in amplitude resolution. That single formula is the entire economics of bit depth: it tells you a 16-bit ECG channel tops out near 98 dB of dynamic range (the ratio between the largest and smallest amplitudes a channel can faithfully represent) on paper, and it warns you that if your analog noise already sits 40 dB below full scale, the bottom eight bits are just digitizing that noise. The honest resolution of a real channel is captured by its effective number of bits (ENOB), which folds analog noise and distortion back in and is almost always smaller than the nameplate \(N\). In short: a sensor's real resolution is written by its noise floor, not by the number stamped on the data sheet.

Common Misconception

"A 24-bit converter gives me 24 bits of real resolution." It does not. The nameplate bit count only fixes the width of the ladder rung \(\Delta\); how many of those rungs carry signal rather than analog noise is the ENOB, which is routinely six to eight bits below the label. A "24-bit" front end whose ENOB is 16 is digitizing its own amplifier hiss in the bottom eight bits, so treating all 24 as meaningful over-trusts the data and wastes storage and radio on random churn.

Key insight

Bit depth is a claim about the amplitude axis exactly as sample rate is a claim about the time axis. Nyquist has an amplitude twin: there is no point spending bits below the analog noise floor, just as there is no point sampling faster than the anti-alias filter passes. The right quantizer resolution is the one whose \(\Delta\) sits just under the smallest signal feature you care about and just under the analog noise, so the LSB dithers the noise rather than the signal. More bits past that point store noise at full price.

Dithering: the counterintuitive fix. When a signal is small relative to \(\Delta\), quantization stops looking like friendly noise and starts looking like distortion: a slow ramp gets rendered as flat plateaus with sudden stair-steps, and those steps are harmonically related to the signal, so they survive averaging and masquerade as real structure. Adding a tiny amount of noise (dither) of order \(\Delta\) before quantizing breaks the correlation, trading a visible staircase for a benign broadband hiss you can then average down. It feels wrong to add noise to reduce error, yet it is standard practice in audio and instrumentation converters for exactly this reason.

Mental Model

Think of an old dial bathroom scale whose needle sticks from friction. Step on it gently and the needle catches on the nearest notch: read once and you get a wrong, quantized value, and reading again gives the same wrong value every time because the friction locks it there. Now tap the scale before you read. The tap adds a little vibration that jiggles the needle past the friction, and different taps push it slightly differently, so the readings scatter around the true weight instead of freezing on one notch. Average a handful of tapped readings and you land closer to the truth than any single stuck reading allowed. The friction is the quantizer step, the tap is the dither, and averaging is what converts that added jiggle back into resolution the plain notch could never show.

Step-Through: quantizing four samples on a 3-bit ladder

Take a 3-bit converter over a full-scale range \(V_{\text{FS}} = 1.0\,\text{V}\) (readings from 0 to 1). The step is \(\Delta = 1.0 / 2^3 = 0.125\,\text{V}\), giving eight rungs at 0, 0.125, 0.250, ..., 0.875, and the error can never exceed \(\pm\Delta/2 = \pm 0.0625\,\text{V}\) unless the input hits the rail. Feed in four true voltages and round each to the nearest rung, index \(k = \operatorname{round}(v/\Delta)\), then clip \(k\) to the range 0 to 7:

The first three errors sit inside \(\pm\Delta/2\) exactly as the uniform-noise model predicts; the fourth breaks the bound because clipping, not rounding, produced it. That single row is why a converter running near full scale needs headroom: past the top rung the error stops being benign quantization noise and becomes hard distortion.

Non-uniform quantization: spend bits where the signal lives

That headroom problem exposes a deeper inefficiency: the evenly spaced ladder we just built reserves as many rungs for the rare large swings near the rail as for the small values a signal spends most of its life inside, which is precisely the wrong way to spend a fixed bit budget.

What it is. A uniform ladder wastes resolution when the signal's amplitude distribution is heavy-tailed: most samples are small, yet a uniform quantizer reserves as many levels for the rare large excursions as for the common small ones. Non-uniform quantization allocates fine steps where samples are dense and coarse steps where they are sparse. The workhorse trick is companding: pass the signal through a compressive nonlinearity (logarithmic \(\mu\)-law or A-law in telephony), quantize the result uniformly, and expand on the far side. An 8-bit \(\mu\)-law channel delivers the perceptual dynamic range of roughly 12 uniform bits for speech, because human loudness perception is itself logarithmic, so four bits of the budget vanish for free the moment you place the levels on a log scale.

Why it matters for sensors. Many physical measurands share this heavy-tailed shape: vibration stays quiet then spikes at faults, and audio and photoplethysmography (PPG) run calm for long stretches between events. Placing quantization levels on a log or perceptual scale preserves small-signal fidelity where the interesting structure hides, yet still survives the rare large event without clipping. The cost is signal-dependent quantization noise, which any downstream calibration or uncertainty model must account for rather than assume a flat \(\Delta^2/12\). Figure 3.5.1 illustrates Non-uniform quantization via companding.

Non-uniform quantization via companding
Figure 3.5.1: Companding places quantization rungs densely near zero and coarsely near the rails, matching a heavy-tailed sensor signal so a fixed bit budget preserves small-signal detail; an 8-bit mu-law channel reaches roughly the perceptual range of 12 uniform bits.

Compression: the same information in fewer bits

Quantization decided how many distinct values exist; compression decides how compactly the resulting stream of values is stored. Two families matter, and confusing them is a classic sensor-pipeline bug.

Compression re-encodes the same sample stream with fewer bits by removing what is predictable or perceptually irrelevant. It matters because radio bandwidth, storage, and battery bottleneck on-device sensors long before compute does. The mechanism models the data's statistics (a predictor for temporal redundancy, a transform for spectral redundancy) and then spends bits in proportion to surprise, so common patterns cost almost nothing and only rare deviations cost their full price. Reach for lossless when every bit must survive for correctness or audit (medical, metrology, forensic streams); reach for lossy only when a measured, bounded distortion is provably harmless to the downstream task.

Two families: lossless and lossy

Lossless compression reconstructs the exact integers you fed in. It exploits redundancy: successive sensor samples are highly correlated, so instead of storing each absolute value you store the small difference from a prediction (delta or linear-predictive coding), then hand those small residuals to an entropy coder (Huffman, arithmetic, or range coding) that spends few bits on the common near-zero residuals and more on the rare large ones. The entropy of the residual stream from Chapter 4 is the theoretical floor; FLAC does this for audio, and the same predict-then-entropy-code recipe typically compresses slowly varying industrial telemetry by three to five times with zero fidelity loss. Because it is exact, lossless compression never changes what a model sees; it only changes the bandwidth bill.

Lossy compression throws away information deemed unimportant to buy far larger savings: quantizing transform coefficients (as MP3 and JPEG do), dropping high-frequency detail, or downsampling. The ratios are dramatic (10x and beyond), but the discarded information is gone, and its absence is structured, not random. This is where sensor AI gets bitten.

Common Misconception

"Compression is just a storage detail; it does not affect the model." True for lossless, dangerous for lossy. A lossy codec introduces artifacts that are correlated with the content: MP3 pre-echo, blocking edges, band-limited plateaus. If those artifacts differ between your training set (recorded raw) and your deployment stream (compressed on a bandwidth-limited device), your model learns the codec, not the phenomenon. Worse, if one class in your dataset happened to be recorded at a higher bitrate, the classifier can key on the artifact and post gorgeous, meaningless accuracy. Codec settings belong in the dataset metadata that Chapter 5 insists you record, precisely so this leakage is auditable.

Real-World Application: FLAC in the Zenodo research-audio archive

Bioacoustic and machine-condition datasets on repositories like Zenodo routinely ship as FLAC rather than WAV, using exactly the predict-then-entropy-code recipe above to shrink multi-terabyte field recordings by roughly two to three times with zero sample loss. Because FLAC is bit-exact, a bird-call detector trained on the compressed archive sees the identical integers a raw WAV would deliver, so the storage saving is free of the codec-leakage risk that lossy MP3 would inject. This is why lossless codecs are the default for any audio corpus meant to be a scientific reference rather than a listening copy.

The noise that engineers pay to add

By a widely repeated account, dither was discovered by accident in World War II. Mechanical analog computers aboard bombers were reportedly more accurate in the air than on the ground, and it took engineers a while to work out why: the constant airframe vibration was jiggling the gears past their static friction, so the machines effectively averaged out their own stiction error. When the bombers landed and the vibration stopped, accuracy fell. The fix, for a while, was literally to bolt small vibrating motors onto the computers. The same counterintuitive principle now lives inside every high-end audio and instrumentation converter, where a deliberately injected noise floor of order one LSB buys back resolution that clean rounding throws away.

Lab: watch ENOB collapse as you add front-end noise

Goal. See empirically that nameplate bit depth is a ceiling, not a promise, by measuring how the effective number of bits (ENOB) of a simulated converter tracks added analog noise rather than the declared resolution.

Tools. Python with numpy and matplotlib; no hardware needed. Budget 15 to 30 minutes.

Procedure. Generate a full-scale sinusoid, add Gaussian analog noise of a chosen root-mean-square (RMS) level, then quantize with a 16-bit version of the quantize function from this section. Measure the achieved SQNR from the residual and invert the \(6.02\,N + 1.76\) rule to read back an ENOB, \(N_{\text{eff}} = (\text{SQNR}_{\text{dB}} - 1.76)/6.02\).

What to vary. Sweep the added analog-noise RMS across several decades, from far below the LSB (\(\Delta/100\)) up to a few percent of full scale, holding the nameplate at 16 bits throughout.

What to observe. When the analog noise sits below \(\Delta\), ENOB stays near 16 and the converter is quantization-limited. As the noise climbs, ENOB falls roughly one bit for every 6 dB of added noise, and a 16-bit part with a few millivolts of front-end hiss delivers only 11 or 12 honest bits, exactly the gap the ENOB warning in this section describes. Plot ENOB against noise RMS on a log axis to see the two regimes meet at a knee near \(\Delta\).

Research Frontier

Hand-designed transforms like MP3 and JPEG are giving way to learned neural codecs that train an encoder, a quantizer, and a decoder end to end. The Descript Audio Codec (Kumar et al., NeurIPS 2023) compresses 44.1 kHz audio to roughly 8 kbit/s with quality that hand-tuned codecs cannot match at that rate, and residual-vector-quantization designs such as EnCodec and SoundStream now underpin generative audio and speech pipelines (as of 2024, this family has largely become the standard front end for speech-language models, with newer streaming codecs such as Mimi, released with Kyutai's Moshi in 2024, pushing full-duplex low-latency operation). For sensor AI this is a double-edged tool: a codec trained on one distribution imprints its own learned artifacts on everything it touches, so the leakage risk this section warns about becomes sharper, not milder, when the codec itself was fit to data. Auditing what a learned codec preserves versus invents, especially for low-amplitude diagnostic features, is an open and active problem.

In practice: the wearable ECG patch that ran out of radio

A cardiac patch samples one ECG lead at 500 Hz with a 24-bit converter and must stream over Bluetooth Low Energy for days on a coin cell. Raw, that is 12 kbit/s per channel of a link whose real budget, after protocol overhead and duty cycling to save battery, is a fraction of that. The first instinct, dropping to a lossy audio codec, is a clinical disaster: the codec smooths the sharp QRS complex (the tall, narrow spike in each heartbeat that marks the ventricles contracting) and can erase the low-amplitude ST-segment shifts (small deviations in the segment just after that spike, a classic marker of reduced blood supply to the heart) that signal ischemia, the very features the device exists to catch. The engineering answer is layered and lossless. First, recognize that the honest ENOB of the front end is about 16 bits, so the bottom of the 24-bit word is amplifier noise and can be masked without losing signal. Then delta-encode successive samples (an ECG baseline barely moves between 2 ms samples) and entropy-code the residuals. The result is a three-to-four-times reduction that fits the radio budget, preserves every diagnostically relevant morphology exactly, and keeps the pipeline defensible when a regulator asks what the compression did to the waveform.

The short simulation below makes the amplitude story concrete: it quantizes a sinusoid at several bit depths, measures the achieved SQNR, and checks it against the \(6.02N + 1.76\) prediction.

import numpy as np

def quantize(x, n_bits, v_fs):
    step = v_fs / (2 ** n_bits)                 # LSB width
    q = np.round(x / step) * step               # snap to nearest rung
    return np.clip(q, -v_fs / 2, v_fs / 2)      # respect the rails

t = np.linspace(0, 1, 20000, endpoint=False)
x = 0.5 * np.sin(2 * np.pi * 7 * t)             # full-scale sinusoid, V_FS = 1.0

for n in (4, 8, 12, 16):
    xq = quantize(x, n, v_fs=1.0)
    noise = xq - x
    sqnr = 10 * np.log10(np.mean(x ** 2) / np.mean(noise ** 2))
    print(f"{n:2d} bits: measured SQNR {sqnr:5.1f} dB   "
          f"predicted {6.02 * n + 1.76:5.1f} dB")
Quantizing a full-scale sinusoid at 4, 8, 12, and 16 bits and comparing measured signal-to-quantization-noise ratio against the \(6.02N + 1.76\) rule. The measured values track the prediction closely, confirming that each added bit buys about 6 dB, and the residual noise array is the quantization noise you can never filter out below.

The right tool: lossless sensor compression without writing a codec

Hand-rolling the delta-plus-entropy pipeline from the ECG story (prediction, residual computation, a working range coder, framing, and a decoder that inverts all of it) is roughly a hundred lines before it round-trips correctly on edge cases. A maintained array codec collapses it to a declarative filter chain:

import numcodecs, numpy as np

samples = (x * 32767).astype(np.int16)          # 16-bit ECG-like stream
codec = numcodecs.Delta(dtype='i2')             # predict: store differences
blosc = numcodecs.Blosc(cname='zstd', clevel=5) # entropy-code the residuals
packed = blosc.encode(codec.encode(samples))
back = codec.decode(blosc.decode(packed))
assert np.array_equal(samples, back)            # exactly lossless
print(f"ratio: {samples.nbytes / len(packed):.1f}x")
Lossless delta-plus-entropy compression of an integer sensor stream with numcodecs. The Delta filter supplies the predictor and Blosc the entropy back end; the round-trip assertion proves nothing was lost. About a hundred lines of custom codec become six, and the fidelity guarantee comes for free.

Exercise

Extend the quantization simulation to study the small-signal regime where dithering matters:

  1. Shrink the sinusoid amplitude to about one LSB of a 6-bit quantizer and plot the quantized output. Describe the staircase you see and explain why it is distortion, not noise.
  2. Add uniform dither of width \(\Delta\) before quantizing, then average many quantized records of the same signal. Show that the averaged, dithered estimate recovers the underlying sinusoid while the undithered one stays locked on plateaus.
  3. Measure the entropy (bits per sample) of the residual stream after delta-encoding your 16-bit quantized signal. How does it compare to 16, and what compression ratio does that entropy floor predict?
Hint

Dither works because it decorrelates the rounding error from the signal, turning a signal-locked staircase into broadband noise that averages toward zero. For part three, estimate entropy as \(-\sum_k p_k \log_2 p_k\) over the histogram of residual values; a well-predicted stream concentrates mass near zero and yields far fewer than 16 bits per sample.

Self-check

  1. Your sensor's nameplate says 24 bits but its ENOB is 16. What is happening in the bottom 8 bits, and why is it wasteful to store and stream them at full price?
  2. Why does adding noise (dither) before a quantizer sometimes reduce error rather than increase it?
  3. A colleague reports 99% accuracy on an audio classifier, but the positive class was recorded at 320 kbit/s MP3 and the negative class at 64 kbit/s. Name the failure and the one metadata field that would have caught it.

Try It: measure the leakage a lossy codec plants in your data

You can reproduce the training-versus-deployment trap from the misconception callout in about twenty minutes with a laptop and standard Python.

  1. Grab a short WAV of two sound classes (for example a clap and a whistle), or synthesize them with numpy as two labeled sinusoid bursts, and save each as 16-bit PCM with scipy.io.wavfile.write.
  2. Encode only the positive class to a low bitrate with ffmpeg -i in.wav -b:a 32k out.mp3 and decode it back to WAV, leaving the negative class raw, so the codec artifact is perfectly correlated with the label.
  3. Extract a simple feature vector per clip (a few mel-frequency cepstral coefficients (MFCCs) via librosa.feature.mfcc, or just a fast Fourier transform (FFT) magnitude histogram) and train a sklearn.linear_model.LogisticRegression on the mixed set.
  4. Record the accuracy, then repeat the whole pipeline with both classes encoded at the same 32k bitrate and retrain.
  5. Compare the two accuracies: the gap is the codec leakage, the phantom signal your first classifier keyed on, and the fix is nothing more than matching the codec settings across classes.

What's Next

Section 3.6 assembles quantized, compressed sample streams into the units a model actually consumes: buffers, windows, and frames. Quantization and compression decided how faithful each sample is and how cheap it is to move; framing decides how those samples are grouped in time before they reach a feature extractor or a network, closing the loop from raw acquisition to model-ready tensors. The bit-budget decisions here also foreshadow the on-device tradeoffs of Chapter 59, where quantizing the model rather than the signal follows the very same 6-dB-per-bit logic.