Set up

A study, from the terminal

What DIMS needs on disk, what it needs in config.json, and which commands turn the two into a dashboard. No builder — if you would rather click through a wizard, the tutorial does that and writes the same study.

The shape of it. A study is a small repository: your recordings under assets/, one config.json describing them, and a pinned copy of the dashboard code under vendor/. The dashboard is static — it reads JSON that the analyses wrote, so nothing runs at view time.

What you need

Python 3.10–3.12

3.13 works for everything except motion capture from video: mediapipe ships no wheel for it yet.

git

DIMS is installed from a checkout, and a study is a repository.

A text editor

config.json is the only file you write by hand.

ffmpeg — optional

Only if you want the builder's video trimming. The analyses and the dashboard do not need it.

DIMS is not on PyPI. dims is taken by an unrelated project and dims-network is unpublished, so it installs from a checkout:

git clone https://github.com/dims-network/dims
pip install -e ./dims

That gives you two commands: dims-case, which creates and maintains a study, and dims-analysis, which runs the analyses.

Want the wizard as well? The builder's dependencies are an extra, so install pip install -e './dims[builder]' instead and you also get dims-builder. Without the extra that command exists but cannot start. The tutorial takes the no-terminal route instead and needs none of this.

1 · Create the study

dims-case new my-study --visibility public   # -> ./case-my-study
cd case-my-study

It takes a name, not a path; --dir puts it somewhere specific. To turn a directory you already have into a study, use dims-case adopt, which never overwrites what is there.

Do not copy the scaffold directory by hand. It has no vendor/, so the page loads eight script tags that 404 and you get a blank screen with nothing worth reading in the console. dims-case new is the command that produces something that runs.
--visibility is the question to answer honestly, once. Choose private if the data identifies anyone — video of faces, named transcripts — and the study is created with a pre-commit hook, a pre-push hook and a CI check that refuse to let data into git. A private study gets no Pages workflow at all, by construction. Going public later is a gated transition, not a setting: data visibility.

2 · The file layout

There is no index. The name is the interface, so a misnamed file is an invisible file.

assets/videos/{videoID}.mp4 assets/timeseries/{videoID}_{dataType}.csv # one measure per file assets/transcripts/{videoID}_transcript.json # optional assets/elan/{videoID}.eaf # optional
FileRequired of it
Video One .mp4 per session, named for its videoID. H.264 in an MP4 container plays everywhere and seeks properly.
Time series CSV with a time column plus one measurement column. The time column is matched case-insensitively and normalised to Time; it is in seconds, ascending — not milliseconds, not frames. Only the first non-time column is read. NaN rows are dropped, not interpolated.
Transcript { "segments": [ {start, end, speaker, text} ] }, times in seconds.
ELAN An .eaf as ELAN saves it: TIME_SLOT elements carrying TIME_VALUE in milliseconds, converted on the way in, and tiers containing ALIGNABLE_ANNOTATION. Only time-aligned annotations are read — a REF_ANNOTATION, which points at a parent annotation rather than at the timeline, is skipped, and a file with no aligned annotations at all is an error rather than an empty tab.
The video and the measurements have to line up already. The dashboard maps a series' Time straight onto the video clock, and there is no alignment tool outside the builder — trimming a video and padding a series are things the wizard does in its step 3. By hand, a session whose camera rolled twelve seconds before its recording started is yours to fix before the files go into assets/: trim the video, or shift and pad the CSV. A mismatch is not an error, it is dead space — video with no data under it, or a signal that stops early.
Neither videoID nor dataType may contain an underscore beyond the one separating them — the name is split on it. dyad01_headSpeed.csv is fine; dyad_01_head_speed.csv is not.

Converting from a tool that writes milliseconds — several EnvisionBox modules among them — is a division and a rename, nothing more. The full rules are in the asset layout contract.

Data that cannot go in the repository private studies Keep assets/ empty and point at where the recordings really live.
cp data.local.json.example data.local.json
{ "assetsRoot": "/Volumes/Data/my-study/assets" }

serve.py and every analysis resolve through that file, so everything runs against the real data with an empty tracked assets/. Nothing has to be copied into the repository — and on a private study, copying it in is exactly what the guards exist to prevent.

Once the assets are right, python build_assets.py --write-manifest writes assets/MANIFEST.json — names, sizes and checksums, never content. On a private study it is the only thing in the repository that says what a complete set of assets is. Commit it.

3 · config.json

Two keys are required. Everything else switches something on.

{
  "videoIDs": ["dyad01", "dyad02"],
  "dataTypes": {
    "dyad01": ["leftHandSpeed", "rightHandSpeed", "sync"],
    "dyad02": ["leftHandSpeed", "rightHandSpeed"]
  },

  "include_elan": true,
  "defaultWindowSize": 5,
  "title": "My study",
  "subtitle": "",
  "authors": "Your name(s)",
  "contacts": "you@example.org"
}

videoIDs and dataTypes describe what is in assets/, and a config that disagrees with the folder produces a tab that draws nothing. dataTypes is an object keyed by session, not a flat list: two sessions rarely carry exactly the same measures, and this is what the time-series tab reads to know what to offer.

The distinction worth reading twice. An analysis key names what to analyse — a list. A tab key is a switch — a boolean. Writing "include_RQA": true is refused, with a message saying what to write instead. Key names are matched case-insensitively, so include_crqa and include_cRQA are the same key.

The full schema, with every constraint, is config.schema.json.

4 · Build, and look

python build_assets.py --check   # what would run, and what is missing
python build_assets.py           # actually run it
python serve.py                  # http://localhost:8000

--check installs nothing and computes nothing. It reports which recordings it can see, which analyses are switched on, and which time series it could not find — the fastest way to discover that a file is misnamed.

serve.py is a static server that also resolves data.local.json and supports Range requests, which video seeking needs. Opening index.html from the filesystem will not work: the browser blocks the fetches.

Each analysis writes one JSON per session, reduced to a few hundred points so a page can draw it. Nothing is computed at view time.

assets/rqa/{videoID}_rqa_data.json assets/crqa/{videoID}_crqa_data.json assets/crosswavelet/{videoID}_crosswavelet_data.json assets/crosswavelet/{videoID}_crosswavelet_full.json # full resolution, for your own analysis

5 · The analyses

Each is a key in config.json and a tab in the dashboard. Add the key, re-run build_assets.py, and the tab appears. Read them in this order: the network is a view of cross-wavelet, so it comes last.

Recurrence — include_RQA seconds per measure Where one signal returns to a state it was in before.
"include_RQA": ["leftHandSpeed", "sync"]

A list of data types — which ones to analyse, not true. Writes assets/rqa/{videoID}_rqa_data.json per session, containing the windowed metrics at full resolution plus a reduced picture for the browser. The recurrence matrix itself is not stored at any resolution: it is quadratic in the length of the recording, and what a reader continues from is the prepared signal and the threshold, from which the matrix is one cdist away.

The window, step and target recurrence rate are tunable under "analysis": {"rqa": {…}}. Determinism and laminarity depend on the target rate, so studies compared with each other must use the same value.

Cross-recurrence — include_cRQA seconds per pair Where two signals repeat each other, and with what delay.
"include_cRQA": [["leftHandSpeed", "rightHandSpeed"]]

A list of pairs, each ["a", "b"]. A flat list of data types is also accepted and expanded to every combination — supported for studies written before pairs existed, but be deliberate: n measures make n(n−1)/2 pairs. Writes assets/crqa/{videoID}_crqa_data.json.

Cross-wavelet — include_crosswavelet minutes per pair with a chance level Which timescales two signals share, and which one leads.
"include_crosswavelet": [["leftHandSpeed", "rightHandSpeed"]],
"analysis": { "crosswavelet": { "mcCount": 100 } }

Same pair shape as cross-recurrence. mcCount is the number of Monte Carlo surrogates behind the coherence chance level: 0 skips it, and only the network's coherence mode reads it — its shared power mode uses a computed level and needs no surrogates. It is the slow part of the whole pipeline — measured on a two-session study with two pairs, 100 surrogates took 9 min 29 s against 1 min 42 s at 20.

The null is cached in ~/.cache/dims/wct_significance, keyed on everything that changes it — series length, wavelet parameters, surrogate count. Re-running the same pairs is close to instant, which means a fast run is not evidence that the analysis was cheap. DIMS_WCT_CACHE_DIR moves the cache; DIMS_WCT_CACHE=0 disables it.

Two files land per session: the reduced payload the browser draws, and _full.json in the same schema at the resolution it was computed at. Continue your own analysis from _full.json.

The cross-effector network — include_network needs cross-wavelet One picture of what is coupled with what, following the playhead.

A view of the cross-wavelet analysis, not a separate one: every edge comes from assets/crosswavelet/{videoID}_crosswavelet_data.json, so an edge exists only where include_crosswavelet asked for that pair — and a pair you did not ask for is a missing edge with nothing saying so. Switching the network on also switches the chance level on at 100 surrogates, because without one a coherence edge cannot be told from coincidence: two unrelated signals score about 0.25, not 0. The tab's shared power mode needs no such null, so it still works in a study built with mcCount: 0.

"include_network": {
  "groups": [{ "label": "Left partner" }, { "label": "Right partner" }],
  "effectors": [
    { "series": "leftHandSpeed",  "group": "Left partner",  "label": "Left hand",  "part": "lefthand" },
    { "series": "rightHandSpeed", "group": "Right partner", "label": "Right hand", "part": "righthand" }
  ],
  "layout": "figure",
  "band": [0.0, 12.0],
  "mode": "coherence",
  "threshold": { "coherence": 0.15, "power": 0.15 }
}

series must be a real data type — a node is matched to its cross-wavelet pairs by that name, so a display name there leaves the node with no edges at all. label is what gets drawn. part is where on the figure it sits: head, lefthand, righthand, torso, hip, foot. band is the period band each edge is averaged over, in seconds — it changes the answer, not just the picture.

"include_network": true means “on, with everything inferred”, which works when your measure names encode person and body part (teacher_righthandspeed) and lands everything in one column when they do not. The full contract is the network tab.

Keeping up, and publishing

dims-case sync .    # refresh the vendored core to the newest release
dims-case check .   # verify nothing under vendor/ was edited

A bot opens the sync as a pull request every Monday. Never edit anything under vendor/: CI rebuilds it from the release tag and compares, so a hand edit is a red build rather than a silent fork. Fix it in the core and bump the pin.

A public study carries the Pages workflow it was created with — push to main and it is live. Any static host works, as long as it serves Range requests, or video seeking will not. A private study has no Pages workflow at all.

Not covered here

FeatureWhere it is
Multi-perspective video — several camera angles per session, with a selector in the dashboard Configured with perspectives, videoPerspectives, videoSrcTemplate and fallbackVideoSrcTemplate. Out of scope for this page; the shapes are in config.schema.json.
Study-owned analyses — an analysis only your study runs An opt/step_*.py plus its requirements, discovered and run after the shared ones. See the step contract.
Trajectory and DTW tabs include_trajectory and include_dtw; the latter is beta. In the schema.

Open the DIMS core →