---
title: "Objective Intelligibility (STOI & ESTOI)"
description: "The two correlation-based objective intelligibility measures for time-frequency weighted noisy speech: STOI (Taal et al. 2011), the clipped per-band envelope correlation, and ESTOI (Jensen & Taal 2016), the row- and column-normalised spectral correlation that tracks modulated maskers, both over the shared 10 kHz one-third-octave 384 ms front end."
references:
  - type: article
    authors: ["Taal, C. H.", "Hendriks, R. C.", "Heusdens, R.", "Jensen, J."]
    year: 2011
    title: "An algorithm for intelligibility prediction of time-frequency weighted noisy speech"
    journal: "IEEE Transactions on Audio, Speech, and Language Processing"
    volume: 19
    issue: 7
    pages: "2125-2136"
    doi: "10.1109/TASL.2011.2114881"
    note: "STOI: the shared front end (10 kHz, 256-sample Hann frames, 15 one-third-octave bands, 384 ms segments), the normalisation and signal-to-distortion clipping (Eqs. 1-4), the per-band envelope correlation (Eq. 5) and the averaged index (Eq. 6)."
  - type: article
    authors: ["Taal, C. H.", "Hendriks, R. C.", "Heusdens, R.", "Jensen, J."]
    year: 2010
    title: "A short-time objective intelligibility measure for time-frequency weighted noisy speech"
    journal: "2010 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)"
    pages: "4214-4217"
    doi: "10.1109/ICASSP.2010.5495701"
    note: "The short conference version of STOI."
  - type: article
    authors: ["Jensen, J.", "Taal, C. H."]
    year: 2016
    title: "An algorithm for predicting the intelligibility of speech masked by modulated noise maskers"
    journal: "IEEE/ACM Transactions on Audio, Speech, and Language Processing"
    volume: 24
    issue: 11
    pages: "2009-2022"
    doi: "10.1109/TASLP.2016.2585878"
    note: "ESTOI: the row- and column mean- and variance-normalisation of the short-time spectrogram (Eqs. 4-7) and the spectral-correlation intermediate index (Eq. 8)."
---

import ThemeImage from '../../../components/ThemeImage.astro';

**STOI** and **ESTOI** are correlation-based objective intelligibility
measures. Each compares a clean reference to a degraded or processed version of
the same speech and returns a scalar with a monotonic relation to the fraction
of words a listener would understand: `1` when the degraded signal equals the
clean one, and near `0` for uncorrelated noise. They work directly on the two
waveforms, which makes them the standard yardstick for **time-frequency
weighted** speech: noise reduction, source separation and binary-mask
processing, where separating the clean speech from its distortion is not
straightforward.

:::note
**STOI/ESTOI vs STI vs SII.** STOI and ESTOI need the clean reference *and* the
degraded signal, and rate the processing between them. The
[Speech Transmission Index](/phonometry/guides/speech-transmission/) rates a
transmission channel from its impulse response, and the
[Speech Intelligibility Index](/phonometry/guides/speech-intelligibility/)
predicts audibility from speech, noise and hearing-threshold spectra. Use
STOI/ESTOI when you have a before/after pair.
:::

:::caution
`pystoi` (a public reimplementation of the authors' MATLAB) is used only as an
external cross-check in the test suite. phonometry reimplements both measures
from the two papers and never imports `pystoi` at runtime; the two agree to
well under `1e-3` on shared inputs.
:::

## 1. The shared front end

Both measures run the same processing before they diverge (Taal et al. 2011,
Section II): resampling to **10 kHz**; a 256-sample (25.6 ms) Hann-windowed,
50 %-overlapping short-time transform zero-padded to 512 points; removal of the
frames whose *clean* energy is more than **40 dB** below the loudest clean
frame; a **15-band one-third-octave** grouping of the DFT magnitudes from a
lowest centre of 150 Hz; and **384 ms** (30-frame) analysis segments as the
unit of comparison. The sample rate of the inputs is free, the library
resamples internally.

```python
from phonometry import stoi

d = stoi(clean, degraded, fs)          # STOI, resampled to 10 kHz internally
print(round(d.value, 3))               # a scalar in roughly [0, 1]
print(stoi(clean, clean, fs).value)    # 1.0  (a signal against itself)
```

## 2. STOI: envelope correlation with clipping

For every band and segment STOI normalises the degraded envelope to the clean
one, clips it at a lower signal-to-distortion bound ($\beta = -15$ dB) so a
fully degraded unit cannot drag the score below its floor, and takes the sample
correlation of the two envelopes (Taal et al. 2011, Eqs. 3-6):

$$
d_{j,m} = \frac{\sum_n (x_j(n) - \bar{x}_j)\,(\bar{y}_j(n) - \bar{\bar{y}}_j)}
               {\lVert x_j - \bar{x}_j \rVert\,\lVert \bar{y}_j - \bar{\bar{y}}_j \rVert}.
$$

The index is the average of those intermediate correlations over all bands $j$
and segments $m$ (Eq. 6). Because the normalisation divides out a per-segment
gain, STOI is **invariant to the overall playback level** of the degraded
signal, and higher SNR gives a monotonically higher score.

```python
import numpy as np
from phonometry import stoi

fs = 10000
rng = np.random.default_rng(1)
clean = rng.standard_normal(3 * fs)
for snr_db in (-10, 0, 10, 20):
    g = 10.0 ** (-snr_db / 20.0)
    noisy = clean + g * rng.standard_normal(clean.size)
    print(snr_db, round(stoi(clean, noisy, fs).value, 3))
```

The `STOIResult` carries the per-band mean correlation (`band_scores`) and the
per-segment scores (`segment_scores`) that average to `value`, and its
`.plot()` draws the per-band intermediate correlation.

## 3. ESTOI: spectral correlation for modulated maskers

ESTOI (`extended=True`) replaces the band-independent correlation with a joint
spectro-temporal one. Within each 384 ms segment it mean- and
variance-normalises the spectrogram **rows** (the band envelopes) and then its
**columns** (the per-frame spectra), and averages the correlation of the
normalised columns (Jensen and Taal 2016, Eqs. 4-8). Making the columns compete
means a masker that leaves quiet gaps, where the clean speech is briefly
audible, is credited for the speech glimpsed there, which STOI's per-band
average largely misses.

<ThemeImage
  src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/stoi_intelligibility.svg"
  alt="Two panels of intelligibility index versus SNR from -15 to 20 dB. Left (STOI): the stationary-masker and modulated-masker curves nearly overlap, so STOI barely separates the two maskers. Right (ESTOI): the modulated-masker curve sits clearly above the stationary one across the whole SNR range, so ESTOI credits the speech glimpsed in the masker's quiet gaps"
  width="94%"
/>

<details>
<summary>Show the code for this figure</summary>

```python
import numpy as np
import matplotlib.pyplot as plt
from phonometry import stoi

fs = 10000
rng = np.random.default_rng(20)
t = np.arange(3 * fs) / fs
# A speech-like clean signal: amplitude-modulated formant-ish tones.
clean = np.zeros_like(t)
for f0 in (200.0, 400.0, 700.0, 1100.0, 1800.0, 2600.0):
    depth = 0.5 * (1.0 + np.sin(2 * np.pi * rng.uniform(2.0, 6.0) * t + rng.uniform(0.0, 2 * np.pi)))
    clean += depth * np.sin(2 * np.pi * f0 * t + rng.uniform(0.0, 2 * np.pi))
p_clean = np.sqrt(np.mean(clean ** 2))

base = rng.standard_normal(clean.size)
gate = 0.5 * (1.0 + np.sign(np.sin(2 * np.pi * 5.0 * t)))   # 5 Hz on/off gate
modulated = base * (0.05 + 0.95 * gate)
snrs = np.arange(-15.0, 20.1, 5.0)

def curve(masker, extended):
    p_m = np.sqrt(np.mean(masker ** 2))
    return [stoi(clean, clean + (p_clean / (p_m * 10.0 ** (s / 20.0))) * masker,
                 fs, extended=extended).value for s in snrs]

fig, (a, b) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)
for ax, extended, title in ((a, False, "STOI"), (b, True, "ESTOI")):
    ax.plot(snrs, curve(base, extended), "o-", label="Stationary masker")
    ax.plot(snrs, curve(modulated, extended), "s--", label="Modulated masker")
    ax.set_title(title); ax.set_xlabel("SNR [dB]"); ax.set_ylim(0, 1); ax.legend()
a.set_ylabel("Intelligibility index")
plt.show()
```

</details>

For additive stationary noise the two measures are interchangeable and STOI is
the lighter default; when the interference fluctuates in time, or when
comparing processors that reshape the speech in time and frequency, prefer
ESTOI.

## See also

- [Speech Transmission Index](/phonometry/guides/speech-transmission/): rates a
  transmission channel from its impulse response or a STIPA recording.
- [Speech Intelligibility Index](/phonometry/guides/speech-intelligibility/):
  predicts intelligibility from speech, noise and hearing-threshold spectra.
- [Filter Banks](/phonometry/guides/filter-banks/): the one-third-octave bands
  the front end groups the DFT into.
- API reference: [`hearing.objective_intelligibility`](/phonometry/reference/api/speech/objective-intelligibility/).
