← Back to Course Home

fNIRS Modified Beer–Lambert Unmixer

An fNIRS optode does not measure oxygenation. It measures how much light of one colour failed to come back, and it does that at two colours. Turning those two numbers into separate oxy- and deoxy-haemoglobin concentrations is a 2×2 linear solve, and whether it works at all depends almost entirely on which two colours you picked. Choose badly and the algebra still runs, still produces smooth plausible traces, and is wrong.

1. The near-infrared window and the two lasers

HbO₂ (oxygenated) HHb (deoxygenated) λ₁ λ₂

Below about 650 nm haemoglobin absorbs so strongly that light cannot cross the head; above about 900 nm water takes over. Between those limits tissue is comparatively transparent, which is the only reason fNIRS exists. Inside that window the two haemoglobin species have different spectra — that difference is the entire measurement. Where the curves cross, at the isosbestic point, they are identical and light of that wavelength cannot tell them apart at all.

Molar extinction coefficients tabulated by S. Prahl from the compilations of W. B. Gratzer (Med. Res. Council Labs, Holly Hill, London) and N. Kollias (Wellman Laboratories, Harvard Medical School), sampled here every 2 nm from 650 to 900 nm. Units are cm−1/M.

2. The Modified Beer–Lambert forward model

Ordinary Beer–Lambert assumes light travels straight from source to detector. In tissue it does not: it scatters, and the mean path is far longer than the optode separation. The modified law patches this with a differential pathlength factor, so the effective path is d × DPF, and adds an unknown scattering loss term G that cancels when you take differences over time. That cancellation is why fNIRS reports changes in concentration and never absolute ones.

ΔA(λ) = [ εHbO(λ) Δ[HbO₂] + εHHb(λ) Δ[HHb] ] · d · DPF(λ)

Condition number of the matrix
Angle between the two rows
Isosbestic point of this data

3. Forward, then back again

A 60-second epoch with two 10-second stimulus blocks. Neural activity drives functional hyperaemia: blood flow overshoots the oxygen actually consumed, so oxyhaemoglobin rises and deoxyhaemoglobin is flushed out. Those true concentrations are pushed forward through the equation above into two attenuation traces, noise is added, and then the 2×2 system is solved to try to get the concentrations back.

ΔA at λ₁ ΔA at λ₂ grey bands = stimulus
recovered Δ[HbO₂] recovered Δ[HHb] ground truth
Same physiology, different noise. Run it a few times.

Try this

Start at the default 760 / 850 pair and look at the two attenuation traces. The 850 nm trace has an obvious response, peaking around 6×10−3 OD. The 760 nm trace is about five times smaller. That is not a weak channel: at 760 nm deoxyhaemoglobin absorbs strongly and oxyhaemoglobin weakly, so the rise in HbO₂ and the fall in HHb partly cancel inside the same measurement. That small trace still carries the information that separates the two species. Delete it and you have one equation and two unknowns, and no amount of processing afterwards will recover oxygenation.

Now press "Both above the isosbestic (830 / 850)". Both wavelengths sit on the same side of the crossing point, where the spectra run almost parallel, so the two rows of the matrix become nearly proportional: the angle between them collapses from 36° to about 2° and the condition number rises from roughly 3 to about 50. At the same noise level the error in recovered HbO₂ goes from a few percent of the true peak to around 40%. Watch how the traces fail — they wander in opposite directions, HbO₂ up wherever HHb goes down. That mirror-image pattern is the classic fNIRS crosstalk artefact, and this is where it comes from. The measurement did not get noisier. The inverse problem got ill-posed.

With that bad pair still selected, set the noise slider to zero. The recovery becomes perfect again. This is the point worth taking away: an ill-conditioned matrix does not corrupt the data, it amplifies whatever error is already there. On real hardware you never get to set the noise to zero, so conditioning is a design decision you make once, when you choose the lasers, and cannot fix afterwards.

Press "One on the isosbestic (796 / 850)" — 796 nm being the nearest 2 nm sample to the 796.8 nm crossing in this table. This still works, which surprises people. At the isosbestic point the two extinction coefficients are equal, so that channel measures total haemoglobin and says nothing about oxygenation — but the other channel is still oxygenation-sensitive, the rows stay independent, and the pair solves. It is measurably worse than 760 / 850 (κ of about 9 against about 3, so noise is amplified roughly three times as much) but nowhere near the failure of 830 / 850. A wavelength that carries no oxygenation information is not the same thing as a useless wavelength.

Set the two DPF sliders far apart, say 3.0 and 9.0. The recovered amplitudes are wrong even though the shapes look right. DPF is not measured in a routine study, it is assumed from published values that depend on wavelength, tissue, and the participant's age. Any error in it propagates straight into the reported micromolar concentrations, which is a large part of why fNIRS results are usually compared within a study rather than across labs.

One thing the model quietly assumes. Every version of the Beer–Lambert law here treats the illuminated volume as homogeneous, when a real channel samples scalp, skull, cerebrospinal fluid, and only then cortex. Much of the signal never reaches the brain at all, which is why modern fNIRS adds short-separation channels to measure the superficial contribution and regress it out.