Live demo: playback of a real 256-subcarrier CSI heatmap
By Wu Jun · Updated 2026-11-02 · 5 min read
This playback uses a real capture (GL-MT6000 / MT7986, 5 GHz HE80, 256 subcarriers, about 60 s). The browser loads a downsampled view of complete measurement groups (347 frames x 64 subcarriers) so it plays smoothly. The source has 2,777 chain records, 347 complete bit-15 groups and one unterminated tail record. The anonymised source and capture method are public [1] [2].
How to read it
Amplitude view (default)
- Time on the horizontal axis, subcarriers on the vertical; brighter means a larger amplitude for that subcarrier;
- a person walking produces vertical bright/dark bands (multipath disturbed); an empty still room flattens out;
- colours are normalised per subcarrier, so absolute values are not comparable across environments.
Doppler / time-frequency view
- the same data run through a short-time Fourier transform: vertical axis is Doppler 0 → Nyquist, brighter means more energy at that frequency;
- the Doppler colours are relative energy: a per-frequency stationary background is removed, then the result is percentile-normalised, so the view highlights energy that changes over time, not absolute power;
- motion energy concentrates at low frequencies (slow body sway, respiration-driven channel fluctuation), and idle stretches go dark overall;
- important limitation: this capture's complete-group measurement rate is about 5.79 groups/s, so the observable Doppler range is only 0–2.89 Hz (Nyquist = rate / 2). That is enough for respiration and slow motion, but it is not a walking-Doppler spectrum — seeing walking-speed Doppler needs a much higher measurement rate or dedicated radar.
The green curve below is shown in both views: it is the motion metric (mean absolute change between consecutive measurements), a cheap "is anything moving?" signal.
A short exercise for first-time readers
Use the replay as an instrument, not as a decorative animation:
- Pause on the first frame and inspect the subcarrier axis. The top and bottom are not “good” and “bad”; they are different parts of the measured channel.
- Start at
1xspeed and watch the heatmap without changing the view. Note whether changes are local to a few subcarriers or form a vertical band across many of them. - Switch to the Doppler view at the same position. A feature that looked like motion in the heatmap should produce changing relative energy somewhere in the low-frequency region. If it does not, do not force an interpretation.
- Increase the playback speed. This changes only presentation time. It does not increase the capture rate or extend the measurable Doppler range.
- Compare the green motion curve with what you observed. A flat section is evidence about this motion metric on this recording; it is not proof that the room was empty.
The useful output of the exercise is a reproducible observation: the selected timestamp, the visible pattern, the view and the expected physical explanation. A screenshot without that context is weak evidence, especially when the colour scale has been normalised.
Why the browser uses a smaller representation
The source contains 256 subcarriers, but the page renders 64 vertical bins and computes a 32-bin Doppler display. Reducing the display grid keeps the page responsive and makes broad changes easier to see. It does not replace the source file or change the checksum. Anyone testing a parser or a feature-extraction method should use the original capture and the published toolkit rather than scraping the canvas image.
The animation is also not a live sensor feed. It does not connect to a router, open a WebSocket, request microphone or camera access, or upload the selected file. The page remains useful as a static visual explanation when JavaScript is unavailable, but the interactive canvases naturally require JavaScript.
Where this data comes from
| Item | Value |
|---|---|
| Capture device | GL.iNet GL-MT6000 (MT7986A + MT7976), OpenWrt + mt76 CSI |
| Band / width | 5 GHz / HE80 / ch44 |
| Subcarriers / chains | 256 / 2 Tx x 4 Rx |
| Measurement rate | about 5.79 complete groups/s (→ Doppler observable up to 0–2.89 Hz) |
| Raw data | 2,777 chain records / 347 complete groups / one tail fragment, 2.8 MB, with SHA-256 |
| In the browser | amplitude 347 frames x 64 subcarriers; Doppler 347 frames x 32 frequency bins (same timeline, background-subtracted + percentile-normalised) |
| Integrity | only bit-15 complete groups are rendered; the unterminated tail is excluded |
What the replay can and cannot show
| Can show | Cannot show |
|---|---|
| how one real capture evolves over time | that a model is accurate in every room |
| whether parsing, downsampling and visualisation agree | that colour is absolute dBm or absolute power |
| relative energy in slow-motion/respiration bands | walking micro-Doppler from a 5.79 Hz sample rate |
| that the source file is unchanged via SHA-256 | that "no motion detected" means an empty room |
The browser does not connect to your router or upload data; the static assets are rendered locally. Use the 10-second sample to validate a parser, or the source and manifest above to recompute the page statistics.
Recompute the evidence from the raw file
A reviewable workflow is deliberately boring: verify the file, validate the container, then compute one number from the bytes and compare it with the manifest.
# 1. Verify the downloaded archive/checksum record
shasum -a 256 -c SHA256SUMS
# 2. Reject framing, chain and timestamp defects before plotting
python3 scripts/check_csi_capture.py csi_demo_60s_anonymized.bin --format markdown
# 3. Parse complete bit-15 groups and recompute the summary
python3 scripts/analyze_csi_capture.py csi_demo_60s_anonymized.bin
The expected top-level observations are 2,777 chain records, 347 closed measurement groups and one tail fragment. Those values must be derived from the file; changing the prose on this page cannot change them. A mismatch is a reason to inspect the parser version and the container offset before trusting any heatmap or Doppler figure.
The toolkit and quality report are public under MIT [1]. For the full ten-minute capture and the dataset DOI, continue to tools and data or the dataset page.
Reproduce it yourself: router CSI guide · Python pipeline · tools and data
References and data sources
Every external claim carries a source and an evidence grade (grading scheme). Grade E marks first-party, reproducible measurements from this project.
- Grade E · first-party measurement
This project's 10-minute raw capture (57,293 records / 600.0 s, SHA-256 00373f83…95e2)
Router CSI Lab · 2026
okwujun.pw/en/datasets/ · verified: Reassembled chunks verified byte-identical (2026-10-11) · The local raw master has SHA-256 00373f83…95e2; the public de-identified derivative has SHA-256 527466ce…a440. They must not be confused. - Grade E · first-party measurement
Router CSI Lab — GL-MT6000 (MT7986) Wi-Fi CSI dataset, 10 s sample + 10 min capture
Router CSI Lab · 2026
doi:10.5281/zenodo.23434789 · verified: CC BY 4.0; SHA-256 published; Zenodo DOI 10.5281/zenodo.23434789 · The dataset has a Zenodo DOI (10.5281/zenodo.23434789) and can be cited directly.