> For the complete documentation index, see [llms.txt](https://docs.anthriq.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.anthriq.com/bxi-studio/developer/overview/concepts/impedance.md).

# Impedance measurement

The impedance stream does not carry ohms. It carries a per-channel magnitude, which becomes an impedance only once the channel's excitation current is known, and becomes a contact verdict only once the device's quality thresholds are known.

The SDK delivers the magnitudes. It does not perform either conversion, because both need an input the SDK has no source for. This page describes the chain so you can apply it yourself; [Measure contact impedance](/bxi-studio/developer/overview/features/impedance.md) covers reading the stream.

## Understand the chain

```mermaid
flowchart LR
  magnitude["magnitude"] -- "excitation current" --> ohms["ohms"]
  ohms -- "quality bands" --> verdict["good / moderate / poor"]
```

| Step              | Needs                          | Where it comes from                                    |
| ----------------- | ------------------------------ | ------------------------------------------------------ |
| Magnitude to ohms | Per-channel excitation current | A calibration run against a channel of known impedance |
| Ohms to a verdict | Contact quality thresholds     | Your own acceptance criteria for the montage           |

Neither input is a constant you can assume. The excitation current varies per channel and per unit, so a default value produces a number that looks like an impedance and is not one.

## Convert magnitude to ohms

The conversion is two steps — magnitude to an RMS voltage, then voltage over current:

```
V_rms = (2 * magnitude / N) * (V_REF / 2^(bits-1)) / sqrt(2)
Z     = V_rms / I_rms
```

| Constant | Value | Meaning              |
| -------- | ----- | -------------------- |
| `V_REF`  | 5.0 V | ADC full-scale span  |
| `bits`   | 24    | ADC resolution       |
| `N`      | 32    | Transform block size |

`I_rms` is the per-channel excitation current, in amperes, from calibration.

### Subtracting the front end's share

The excitation current flows through the analog front end as well as the electrode, so this calculation yields **total series impedance**. Subtracting the front end's own share leaves the electrode alone, which is what an operator adjusting a contact cares about.

That share is the same 4990 Ω reference a calibration run uses as its known impedance, which is not a coincidence: a shorted channel presents exactly that, so with the subtraction applied it reads near zero instead of near 4990.

> **Note:** Do not clamp a negative result to zero. A persistently negative reading means the subtraction or the calibration is wrong, and hiding it at zero disguises that as a perfect contact.

## Calibrate to find the excitation current

Calibration inverts the conversion. Short a channel so its impedance is known at 4990 Ω, measure the magnitude it produces, and solve for the current:

```
I_rms = V_rms / 4990
```

A single capture is noisy, so a usable run is structured to resist it:

* Take several independent cycles with a quiet gap between them, rather than one long capture.
* Within a cycle, take a **median of segment medians** instead of a plain median. Medianing twice weights each stretch of time equally rather than each sample, so a burst of noise confined to part of the capture cannot pull the result.
* Exclude error-flagged samples before any statistics are taken.
* Keep the per-cycle currents, not just their average. The spread across cycles is the honest measure of confidence: tight means a trustworthy current, wide means the capture was noisy or the channel was not properly shorted.

Calibration is a property of the hardware, not of a session. Store the result and reuse it until the hardware changes.

## Classify contact

Once you have ohms, a verdict is a threshold comparison against bands you choose for your montage — electrode type, gel, and session length all move what counts as acceptable.

Keep an explicit `unknown` state for a channel with no reading, no calibrated current, or a negative value. It is a real state, not an error: an electrode that has not reported is not a bad electrode, and showing it as poor sends someone to adjust hardware that is fine.

## Downsample for display

The stream runs faster than a display needs. Reduce the rate by **aggregating**, not decimating: make each output the median of segment medians of the samples it replaces.

That distinction matters. Decimating keeps one surviving sample and discards the rest, which passes every rate check while throwing away exactly the noise rejection the aggregate provides.

Carry a sample count alongside each aggregate, so a consumer can tell a solid average from one built on a handful of samples that survived error rejection.

## Next steps

* [Measure contact impedance](/bxi-studio/developer/overview/features/impedance.md)
* [Stream lifecycle](/bxi-studio/developer/overview/concepts/streams.md)
* [Device support](/bxi-studio/developer/overview/concepts/device-support.md)
