gifti
tit.scene.gifti ¶
GIfTI serialisation for the scene panes (plan of record §1, decision E7).
TVSC1 (:mod:tit.scene.tvsc) is the frozen compatibility format from the
retired desktop renderer path. The run-page panes now use the Tetravox
embed, whose engine reads no bespoke format at all -- so the scene service
keeps everything that makes it worth having (cropping a 184 MB mesh to skin +
cortex, simplifying to the §S3 budget, carrying an atlas' labels onto the
surface it actually serves, and the fingerprinted cache) and changes only the
bytes it hands out.
What the engine parses, read out of its own source rather than assumed
(crates/tvx-mesh-io/src/gifti.rs in the Tetravox tree, at the protocol-2
tag lane T packed):
- the format is sniffed by content, not by a file name --
sniff()triesgifti::looks_likebefore any extension hint -- soGET /api/scene/surface?subject=ernie&part=skin&format=giineeds no.giiin its URL; - three encodings:
ASCII,Base64BinaryandGZipBase64Binary, and the last is a zlib stream (ZlibDecoder), not gzip; NIFTI_INTENT_POINTSET(3 components),NIFTI_INTENT_TRIANGLE(3 components) and anything else as a per-vertex node field;- a
NIFTI_INTENT_LABELarray plus a<LabelTable>is remapped to a dense index (position in the table) and the table becomes the layer's palette. A value the table does not name maps to dense 0, so the table's first entry has to be the "no region" one or every unlabelled vertex is painted with the first region's colour -- a whole cortex in one colour, and it looks plausible.
Why this module writes the XML itself rather than calling nibabel:
- :mod:
tit.sceneis deliberately split so that everything except :mod:tit.scene.buildneeds numpy only --tests/conftest.pyreplacesnibabelwith aMagicMock, so a nibabel-based writer could not be exercised at all by the host suite, and this is the half whose bug is invisible until a renderer draws garbage. - The engine parses a documented subset. Writing that subset directly means the bytes are pinned by a test in this repository instead of by whatever nibabel's defaults happen to be.
The bytes are checked against a real nibabel (which the container has
and the host suite mocks) by tests/test_scene_gifti.py, which skips when
nibabel is a mock -- so the round trip is proved on the interpreter that ships.
LabelEntry
dataclass
¶
One <Label> row: its key, its name and its 0..1 RGBA.
label_table_from_legend ¶
label_table_from_legend(legend: list[dict], *, unlabelled_name: str = 'unlabelled', unlabelled_rgba: tuple[float, float, float, float] = (0.6, 0.6, 0.62, 0.0)) -> list[LabelEntry]
A <LabelTable> from build_labels' own legend rows.
legend rows carry label (the uint16 value in the payload),
name and color as "#rrggbb". The result always begins with
:data:NO_REGION, whatever the legend holds.
Source code in tit/scene/gifti.py
encode_surface ¶
encode_surface(positions: ndarray, indices: ndarray | None = None, labels: ndarray | None = None, label_table: list[LabelEntry] | None = None, *, label_name: str = 'regions') -> bytes
Serialise one surface (and optionally its per-vertex labels) as GIfTI.
Parameters mirror :func:tit.scene.tvsc.encode exactly, on purpose: this
is a change of serialisation and nothing else, so the same guards apply and
the same call sites work.
positions
(V, 3) world-RAS millimetres, cast to float32. A non-finite
coordinate is refused rather than shipped: a NaN collapses the
engine's bounding box and the pane then frames nothing, with no error
anywhere.
indices
(T, 3) triangle corners, None for a labels-only payload. Every
index must be < V; the engine checks this too and fails the whole
load, so failing here names the surface instead.
labels
(V,) per-vertex region ids. Written as NIFTI_TYPE_INT32
(GIfTI's label type) with the <LabelTable> in label_table.
label_table
The <Label> rows. Required when labels is given: without a
table the engine treats the array as a continuous scalar and the
regions are painted through a colormap instead of their own colours.
Source code in tit/scene/gifti.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |