Dashboard Generation Developer Guide
Overview
The empty-room data quality dashboards are generated from the BIDS empty-room
dataset hosted on NYU Box. The scripts live under
docs/source/scripts/dashboard-generating-scripts and are run by the
Empty-room Data Quality Dashboard GitHub Action
(.github/workflows/empty-room-dashboard.yml) every day at 05:00 UTC. The
Action downloads any recording not yet present in the metrics CSV, computes
the metrics, regenerates the figures and commits the resulting CSV, HTML and
PNG files back to main (the commit carries [skip ci]; the daily docs
deploy at 06:00 UTC publishes the refreshed files to the website). The docs
build itself never talks to Box, so a Box or network problem can never break
the website build.
The stack being used comprises:
data source: NYU Box (BIDS empty-room dataset)
compute: MNE, NumPy, SciPy, pandas
figures: Plotly (interactive HTML) and Matplotlib (sensor-topography PNG)
automation: GitHub Actions
rendering: Sphinx, deployed to Cloudflare Pages (public and internal sites)
The scripts are:
config.pyall paths, Box folder identifiers, the per system definitions (acq entity, file extension, thresholds, output CSV paths) and the analysis parameters (window, PSD settings, line frequency).
paths are anchored to the file location, so the scripts run the same from the repository root, from
docs/sourceor from the Action.
utilities.pyauthenticates to Box from a single JWT settings JSON (
BOX_CLIENT_SDK_CONFIG);resolves the empty-room dataset folder (see Authentication below), walks it recursively (
sub-emptyroom/ses-*/meg) and classifies recordings by the BIDSacqentity (acq-kitgives.con,acq-opmgives.fif); derivatives carrying aproc-entity are skipped so raw room noise is tracked consistently;computes per sensor RMS, PSD noise floor and line-noise, plus the summary average, variance and maximum, and writes the per recording and per sensor CSVs;
renders the sensor-topography maps (the per recording PNGs, the recent-sessions grid and an interactive hover version with a session dropdown, built from the per channel CSV plus the stored sensor positions), the sensor by session heatmap, the amplitude spectrum and the metric time-series figures. Sensor positions are written to
<system>-sensor-positions.csvwhenever a recording is processed, so the interactive map can also be rebuilt by runs that download nothing new; for KIT the builtinKIT-ADlayout serves as fallback until that file exists.
update_data_quality_dashboards.pythe entry point. Run it with no arguments for a Box run, or with
--local-dirto process a local BIDS tree for testing (no Box credentials needed).--systems kit,opmselects systems.
generate_system_status_dashboards.pybuilds the system status cards (KIT, OPM, QD helium, Vpixx) from the status CSVs under
docs/source/data/system-status-dashboards. The output is small self-contained HTML with no Plotly dependency; edit the CSVs to record a new week.
Authentication and configuration
In CI the credentials come from repository Actions secrets:
BOX_CLIENT_SDK_CONFIG: the complete Box JWT app settings JSON, including theappAuthprivate key block (a JSON downloaded at keypair generation time; a later re-download from the Box console has an emptyappAuthand will not work).BOX_EMPTYROOM_FOLDER_ID(recommended): the folder ID of the dataset root that containssub-emptyroom, taken from the folder’s URL in the Box web app.BOX_MEG_DATA_PARENT_FOLDER_ID: fallback starting point. When the direct ID is not set, the resolver walksData/empty-roomfrom this parent, then triesempty-roomdirectly, thenMEG/Data/empty-room, and finally accepts the parent itself if it already contains the BIDS subject folders.DASHBOARD_PUSH_TOKEN: an administrator personal access token used by the workflow to push the refreshed artifacts to the protectedmainbranch.
For a manual run, place the same JWT JSON in box_config.json next to the
scripts (this file is git-ignored and must never be committed).
import json, os
from boxsdk import Client, JWTAuth
settings = json.loads(os.environ["BOX_CLIENT_SDK_CONFIG"])
client = Client(JWTAuth.from_settings_dictionary(settings))
Running locally
pip install "boxsdk[jwt]" mne plotly pandas numpy scipy matplotlib
cd docs/source/scripts/dashboard-generating-scripts
# Offline test against a local BIDS tree (no Box needed):
python update_data_quality_dashboards.py --local-dir /path/to/empty-room --systems kit
# Full Box run (requires BOX_CLIENT_SDK_CONFIG):
python update_data_quality_dashboards.py --systems kit,opm
Outputs
docs/source/data/data-quality-dashboards/<system>-empty-room-metrics.csv: one row per recording.docs/source/data/data-quality-dashboards/<system>-per-channel-metrics.csv: one row per sensor per recording.docs/source/data/data-quality-dashboards/<system>-empty-room-metrics-recent.csv: the always-visible most recent rows of the data quality page’s table.docs/source/data/data-quality-dashboards/<system>-empty-room-metrics-display.csv: the full history table shown in the collapsed dropdown.docs/source/data/data-quality-dashboards/<system>-sensor-positions.csv: 2D sensor layout used by the interactive topomap.docs/source/_static/2-data-quality-dashboards/<system>_*.html: interactive figures.docs/source/_static/2-data-quality-dashboards/<system>_topomap_recent.png: sensor-topography maps.
Adding or changing a metric
Add the computation in compute_metrics in utilities.py, add the column to METRIC_COLUMNS (and
to DISPLAY_COLUMNS if it should appear in the table), document it in
docs/source/data/data-quality-dashboards/noise_metrics.csv, and add a figure builder if a plot is wanted.