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.
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.3.13 works for everything except motion capture
from video: mediapipe ships no wheel for it yet.
DIMS is installed from a checkout, and a study is a repository.
config.json is the only file you write by
hand.
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.
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.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.
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.There is no index. The name is the interface, so a misnamed file is an invisible file.
| File | Required 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. |
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.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.
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.
config.jsonTwo 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.
"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.
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.
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.
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.
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.
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.
~/.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.
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.
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.
| Feature | Where 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. |