viewers
tit.server.routes.viewers ¶
/api/view/{kind} and /api/view/args (v1).
The ViewSpec is built entirely in :mod:tit.viewspec (layer rules, LUT
lookups, the six audit-bug fixes, percentile resolution, the Tetravox
ViewSpec v2 scene document); this module only maps the query params
to build_view.
D3 (docs/dev/DECISIONS.md § 2026-09-03 (One Docker image and a real development loop)): the external Freeview/Gmsh
launch routes (POST /api/viewers/freeview, POST /api/viewers/gmsh),
_require_x11 and the viewer job-kind submission they drove are
removed -- there is no X11 in this runtime. freeview_args itself
stays on the response for one release (deprecated) so an old client
mid-migration does not break; :func:view_args still exists to preview it.
V2 (docs/dev/DECISIONS.md § 2026-09-06 (Native panes, job rows and notebooks)): viewing is the
host-installed Tetravox desktop app, not an embed served from this
origin. POST /api/view/open (bottom of this module) writes the scene
document that app opens; it still launches nothing server-side, because the
app it writes for is not on this side of the container boundary.
Electrode overlay creation (v1 gap, pages/viewer/PARITY.md #1): the
Viewer page's "Create/refresh electrode overlay" action is not a viewer
launch -- it is a tools job that runs
tit.tools.electrode_overlay as a plain module invocation
(tit/jobs/kinds.py's tools branch: simnibs_python -m <module>
*args), the same way any other standalone tit.tools.* script would be
run. The client submits::
POST /api/jobs
{
"kind": "tools",
"config": {
"module": "tit.tools.electrode_overlay",
"args": [
"<sim_dir>/documentation/config.json",
"<m2m_dir>/T1.nii.gz",
"<sim_dir>/<TI|mTI>/montage_imgs/electrode_overlay_subject.nii.gz"
]
},
"subject_ids": ["<id>"]
}
(see tit/tools/electrode_overlay.py::main for the exact positional
config/reference/output argv and the optional --montage/
--eeg-positions-dir flags). Once that job succeeds,
GET /api/catalog/electrode-overlays?subject=&simulation=
(tit.catalog.electrode_overlays) reports the resulting file's
presence/path per TI/mTI mode -- tit.viewspec._electrode_overlay_layer
already builds the ViewSpec layer for it once the file exists on disk, so no
viewspec change was needed for the viewing half of this gap, only the
listing half.
view_args ¶
{viewspec} -> {freeview_args, freeview_command, scene}.
Exists so the Viewer page's "preview command" panel can show the real
command -- generated by the same :func:viewspec.to_freeview_args a
launch would use, on the same jailed/percentile-resolved spec -- instead
of a hand-rolled TypeScript mirror that can (and did) drift from the
server's actual argument grammar (ra_13 finding 9). Read-only: this
never submits a job and launches nothing; layer paths are jailed but
nothing runs.
Source code in tit/server/routes/viewers.py
viewer_scene_dir ¶
viewer_scene_dir() -> str
<project>/code/ti-toolbox/viewer/ -- sibling of config/ and jobs/.
Source code in tit/server/routes/viewers.py
checked_viewer_path ¶
path with its parent resolved, or 403 if it or its leaf's target leaves the project.
The leaf is kept as named (:func:tit.paths.resolve_leaf_within), so a
delete or replace acts on a stored alias rather than on what it points to.
Source code in tit/server/routes/viewers.py
atomic_viewer_write ¶
Replace a whole document without following a predictable temporary-file symlink.
Source code in tit/server/routes/viewers.py
localise_scene_paths ¶
localise_scene_paths(scene: dict[str, Any], container_root: str, host_root: str | None) -> dict[str, Any]
Every dataset/sidecar URL in scene rewritten to a filesystem path.
Returns a new document; scene is not mutated (the same object is still
the response body of GET /api/view/{kind}, where the URL form is
correct). With no host_root the container path is used, which is right
for a browser-mode download opened on a machine that mounts the project at
the same place, and honest everywhere else -- Tetravox says which file it
could not find.
Source code in tit/server/routes/viewers.py
native_scene ¶
Resolve scene data into host paths, staging bundled resources in the project.
Source code in tit/server/routes/viewers.py
export_scene ¶
Preserve camera/layers and write host-addressed files for the native viewer.
Source code in tit/server/routes/viewers.py
view_open ¶
{kind, subject, ...} -> {name, path, host_path, scene, view, files}.
Launches nothing, and never could: the server has no display (D3). One resolution produces two addressings of the same scene --
view-- every dataset an/api/files/raw/...URL. This is what the Viewer sub-page reads to describe and preview the selection, fetching those bytes back through this origin.scene-- the same document with every path re-rooted onto the host, also written to<project>/code/ti-toolbox/viewer/<kind>.tetravox.jsonso the file can be opened by a desktop Tetravox or kept as a record of what was viewed.
They come from one build_view call on purpose. Two calls could resolve
differently -- a job finishing between them is enough -- and then the list
the page shows, the file on disk and the scene on screen would disagree
about what is in the scene, with nothing to say which was right.
Source code in tit/server/routes/viewers.py
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 | |
viewer_candidates ¶
viewer_candidates(subject: Annotated[SubjectId | None, Query()] = None, simulation: Annotated[EntityName | None, Query()] = None, space: str | None = Query(None)) -> dict[str, Any]
A read: it opens nothing and writes nothing.
Only files a scene can actually use are listed (volumes and meshes), each with its size, because the point of the picker is to choose without guessing -- and one of these files is routinely 64 MB.