ELISA

The enzyme-linked immunosorbent assay (ELISA) uses the exquisite specificity of an antibody–antigen bond to detect and quantify a target, reading it out as a color change driven by an enzyme. Run in 96-well plates, it is the workhorse of serosurveillance (measuring who has antibodies to a pathogen) and of antigen detection, cheap and scalable enough to process thousands of samples a day.

An ELISA standard curve fit with the four-parameter logistic model: optical density rises sigmoidally with analyte concentration, with the inflection at the EC50.

The core idea and the main formats

Every ELISA immobilizes something on the plate, washes away everything that does not stick, and detects what remains with an enzyme-conjugated antibody that converts a colorless substrate into a colored product. The optical density (OD) measured by a plate reader is proportional to how much target was captured.

Turning optical density into concentration

OD does not rise linearly with concentration — it saturates. The standard model for an ELISA calibration curve is the four-parameter logistic (4PL):

OD(x)=d+ad1+(x/c)b,\text{OD}(x) = d + \frac{a - d}{1 + (x/c)^{b}},

where aa is the bottom (blank) signal, dd is the top (saturation), cc is the EC50 (the concentration at the inflection point), and bb is the Hill slope. You fit the 4PL to a dilution series of known standards, then invert it to read unknown concentrations off their ODs. Reliable quantification is only possible within the curve’s dynamic range — samples above saturation must be diluted and re-run.

Cutoffs for qualitative serology

For yes/no serosurveys the readout is dichotomized with a cutoff, often set from a panel of known negatives (e.g. mean + 3 SD of the negative controls). Every cutoff trades sensitivity against specificity, and because seroprevalence surveys often run at low true prevalence, even a small false-positive rate can dominate the positives — which is why raw seroprevalence is usually adjusted for assay performance.

A worked example

We fit a 4PL curve to a seven-point standard series and back-calculate the concentration of an unknown from its OD.

In code

R

fourpl <- function(x, a, d, c, b) d + (a - d) / (1 + (x / c)^b)
conc <- c(1, 3, 10, 30, 100, 300, 1000)
od   <- c(0.06, 0.12, 0.34, 0.98, 1.9, 2.7, 3.1)

fit <- nls(od ~ fourpl(conc, a, d, c, b),
           start = list(a = 0.05, d = 3.2, c = 30, b = 1))
coef(fit)   # bottom, top, EC50, Hill slope

Python

import numpy as np
from scipy.optimize import curve_fit

def fourpl(x, a, d, c, b):
    return d + (a - d) / (1 + (x / c) ** b)

conc = np.array([1, 3, 10, 30, 100, 300, 1000.0])
od   = np.array([0.06, 0.12, 0.34, 0.98, 1.9, 2.7, 3.1])

(a, d, c, b), _ = curve_fit(fourpl, conc, od, p0=[0.05, 3.2, 30.0, 1.0], maxfev=10000)
print(f"bottom={a:.2f}  top={d:.2f}  EC50={c:.1f} ng/mL  hill={b:.2f}")

# invert the curve to read an unknown OD back to a concentration
od_unknown = 1.2
conc_unknown = c * ((a - d) / (od_unknown - d) - 1) ** (1 / b)
print(f"OD {od_unknown} -> {conc_unknown:.1f} ng/mL")
bottom=0.01  top=3.30  EC50=73.0 ng/mL  hill=1.04
OD 1.2 -> 42.4 ng/mL

Julia

using LsqFit
fourpl(x, p) = p[2] .+ (p[1] .- p[2]) ./ (1 .+ (x ./ p[3]) .^ p[4])
conc = [1, 3, 10, 30, 100, 300, 1000.0]
od   = [0.06, 0.12, 0.34, 0.98, 1.9, 2.7, 3.1]

fit = curve_fit(fourpl, conc, od, [0.05, 3.2, 30.0, 1.0])
fit.param   # bottom, top, EC50, Hill slope

Trade-offs & resource considerations

Why it matters

ELISA is how we measure population immunity. Serosurveys built on ELISA tell us what fraction of a population has been infected or vaccinated — the susceptible pool that drives whether transmission can be sustained — and antigen-capture ELISAs detect active infection where a rapid test is not sensitive enough.