scene
tit.scene ¶
tit.scene — the slim scene service behind the v3 3D panes.
Plan of record: docs/dev/DECISIONS.md § 2026-09-04 (Scene service and retained pages) §2, decisions S1-S3.
Non-goals, written here so they stay non-goals (decision S1): no volume slicing, no colormaps, no field overlays, no publication screenshots, no layer tree. This module exists so a user can choose — electrodes on the Simulator, a target on the Optimizer, a verified ROI on the Analyzer. Everything a viewer does belongs to Tetravox, on the Viewer page and in the Results preview; building it a second time here is exactly what the microservice split exists to prevent.
What it does do: extract the two surfaces a pane draws (skin tag 1005, grey
matter tag 1002) out of a 184 MB head mesh, get grey matter under the §S3
budget, carry an atlas' per-vertex labels onto the surface it actually serves,
read an EEG net's electrode coordinates, and cache all of it under
.ti-toolbox/cache/scene/.
Module map, split along the line the host test suite can cross:
=================== ========================================================
module needs
=================== ========================================================
:mod:~tit.scene.tvsc numpy only — the TVSC1 wire format (§2.3)
:mod:~tit.scene.simplify numpy only — grid vertex clustering to the §S3 budget
:mod:~tit.scene.cache stdlib only — fingerprints, atomic publish, build lock (§2.2)
:mod:~tit.scene.build simnibs / nibabel / scipy, all imported inside functions
=================== ========================================================
tests/conftest.py replaces simnibs, nibabel and scipy with
MagicMocks, so the first three are fully exercised by the host suite and
:mod:~tit.scene.build's numeric core is reached through its pure helpers
(:func:~tit.scene.build.labels_from_nearest,
:func:~tit.scene.build.parse_electrode_csv,
:func:~tit.scene.build.parse_lut_text). What only real data can prove — that
labels land on the right region and electrodes land on the skin — is
tests/test_scene_realdata.py, gated on TIT_SCENE_TESTDATA.
HTTP surface: :mod:tit.server.routes.scene.
TvscPayload
dataclass
¶
One decoded TVSC1 blob.
indices is (0, 3) for a labels-only payload and labels is
None when the flag bit is clear -- a caller never has to look at
flags itself.
decode ¶
decode(blob: bytes | bytearray | memoryview) -> TvscPayload
Parse TVSC1 bytes back into arrays, or raise ValueError.
Every length is checked against the header before a view is taken: a truncated blob (a half-written cache file, a proxy that cut the body) must fail loudly here rather than hand a short array to a caller that then indexes past it.
Source code in tit/scene/tvsc.py
encode ¶
Serialise one surface (or labels-only) payload to TVSC1 bytes.
Parameters¶
positions:
(V, 3) world-RAS millimetres. Cast to float32; a
non-finite coordinate is refused rather than shipped, because a
NaN position silently collapses a renderer's bounding sphere and
the pane then draws nothing with no error anywhere.
indices:
(T, 3) triangle corners, or None/empty for a labels-only
payload. Every index must be < V: an out-of-range index reads
past the vertex buffer in WebGL and the driver's behaviour there is
undefined.
labels:
(V,) per-vertex uint16 region ids, or None. 0 means
"no region" by convention (see :mod:tit.scene.build).