Skip to content

scene

tit.analyzer.scene

The one scene an analysis leaves behind: the field masked to its ROI.

An analysis folder is data plus exactly one scene.tetravox.json::

results.csv, analysis.json      the numbers
roi_overlay.nii.gz              voxel: the field, zero outside the ROI
roi_overlay.msh (+ .msh.opt)    mesh:  the surface with ``<field>_ROI`` node data
scene.tetravox.json             this: the overlay over the anatomy, cursor on the ROI

The scene is a few kilobytes and points at the overlay the analysis wrote anyway -- it copies nothing and rasterises nothing. The overlay is the dataset because it is the analysis's own product: a picture drawn from a different file than the table came from would be a picture that could contradict it.

Voxel: the subject's T1 under the overlay, inferno scaled from the field's display floor to its p99.9 inside the ROI (never the max: the max is one voxel), zeros hidden so nothing but the ROI is painted. Mesh: the whole cortex once, translucent and unpickable, and once more coloured by <field>_ROI with every node outside the ROI (exactly 0) hidden -- so what shows is the ROI's field inside a see-through cortex, which transparency: peel renders in order. A bare .msh open cannot carry that intent (Tetravox ignores View[n].Visible on open), which is why it travels in the scene.

Framing reuses :mod:tit.figures.roi_plate -- the cursor lands on the ROI's centroid, snapped into it, and the zoom fills the panel -- and the file is assembled by the same function the ROI scene uses, so both look alike. Like every artefact a person looks at, this never fails a job: a failure is one log line.

write_voxel_scene

write_voxel_scene(*, out_dir: str, anatomy: str, overlay: str, roi_mask, affine, roi_values, field_name: str, region_name: str, meta: dict) -> str | None

The scene for a voxel analysis: T1 plus roi_overlay.nii.gz.

roi_mask/affine/roi_values are the analysis's own, so the cursor is placed on the voxels the table was computed from and the window is read off the same numbers.

Source code in tit/analyzer/scene.py
def write_voxel_scene(
    *,
    out_dir: str,
    anatomy: str,
    overlay: str,
    roi_mask,
    affine,
    roi_values,
    field_name: str,
    region_name: str,
    meta: dict,
) -> str | None:
    """The scene for a voxel analysis: T1 plus ``roi_overlay.nii.gz``.

    ``roi_mask``/``affine``/``roi_values`` are the analysis's own, so the cursor
    is placed on the voxels the table was computed from and the window is read
    off the same numbers.
    """
    if not _enabled():
        return None
    try:
        import numpy as np

        from tit.figures.roi_plate import (
            _dataset,
            _volume_bounds,
            _volume_layer,
            anatomy_layer,
            assemble_scene,
            plan_framing,
        )

        mask = np.asarray(roi_mask, dtype=bool)
        plan = plan_framing(mask.astype(np.int16), affine, names={1: region_name})
        if plan.empty:
            logger.warning("analysis scene not written: %s", plan.reason)
            return None
        roi_values = np.asarray(roi_values, dtype=float)
        lo, hi = _window(roi_values)
        datasets = [
            _dataset(0, anatomy, out_dir, "volume"),
            _dataset(1, overlay, out_dir, "volume"),
        ]
        layers = [
            anatomy_layer(anatomy),
            _volume_layer(
                1,
                "ds1",
                os.path.basename(overlay),
                colormap=FIELD_COLORMAP,
                opacity=0.85,
                scale={"kind": "linear", "lo": lo, "hi": hi},
                threshold=_hide_below(ZERO_EPSILON),
                showColorbar=True,
            ),
        ]
        row = plan.rows[0]
        scene = assemble_scene(
            datasets=datasets,
            layers=layers,
            cursor=[float(v) for v in row.cursor_ras],
            mm_per_px=float(row.mm_per_px),
            bounds=_volume_bounds(anatomy),
            meta=_meta(meta, plan, field_name, roi_values, lo, hi, "voxels"),
            layout="2x2",
        )
        return _write(out_dir, scene, f"{field_name} in {region_name}")
    except Exception as exc:  # noqa: BLE001 - a scene never fails a job
        logger.warning("analysis scene could not be written: %s", exc)
        return None

write_mesh_scene

write_mesh_scene(*, out_dir: str, mesh: str, roi_coords, node_coords, roi_values, field_name: str, region_name: str, meta: dict, normal_max: float | None = None) -> str | None

The scene for a mesh analysis: roi_overlay.msh twice over.

roi_coords are the ROI's node coordinates (the cursor), node_coords every node's (the camera fit), roi_values the field at the ROI's nodes. normal_max (the ROI's largest positive TI_normal) adds a hidden third layer for the TI_normal_ROI node data the overlay carries.

Source code in tit/analyzer/scene.py
def write_mesh_scene(
    *,
    out_dir: str,
    mesh: str,
    roi_coords,
    node_coords,
    roi_values,
    field_name: str,
    region_name: str,
    meta: dict,
    normal_max: float | None = None,
) -> str | None:
    """The scene for a mesh analysis: ``roi_overlay.msh`` twice over.

    ``roi_coords`` are the ROI's node coordinates (the cursor), ``node_coords``
    every node's (the camera fit), ``roi_values`` the field at the ROI's nodes.
    ``normal_max`` (the ROI's largest positive ``TI_normal``) adds a hidden
    third layer for the ``TI_normal_ROI`` node data the overlay carries.
    """
    if not _enabled():
        return None
    try:
        import numpy as np

        from tit.figures.roi_plate import _dataset, assemble_scene, plan_surface

        plan = plan_surface([np.asarray(roi_coords, dtype=float)], names=[region_name])
        if plan.empty:
            logger.warning("analysis scene not written: %s", plan.reason)
            return None
        roi_values = np.asarray(roi_values, dtype=float)
        # The mesh carries only the ROI, so its bar runs from 0 to the ROI's max.
        lo, hi = 0.0, round(float(np.max(roi_values)), 6)
        dataset = _dataset(0, mesh, out_dir, "mesh")
        opt = f"{mesh}.opt"
        if os.path.isfile(opt):
            dataset["sidecars"] = {"opt": {"path": os.path.basename(opt)}}
        name = os.path.basename(mesh)
        layers = [
            # The whole cortex, see-through and not in the way of a click.
            _mesh_layer(0, name, opacity=0.25, pickable=False, colorMode="solid"),
            # The ROI's field on top; every node outside the ROI is 0 and hidden.
            _mesh_layer(
                1,
                f"{field_name}_ROI",
                colorMode="field",
                field={
                    "source": "node",
                    "name": f"{field_name}_ROI",
                    "component": "mag",
                },
                scale={"kind": "linear", "lo": lo, "hi": hi},
                # Twice the max: any finite bound above every value, with the
                # peak node itself still inside the ramp.
                threshold=_hide_below(ZERO_EPSILON, round(2.0 * hi, 6)),
                showColorbar=True,
            ),
        ]
        if normal_max is not None and normal_max > 0:
            layers.append(
                _mesh_layer(
                    2,
                    "TI_normal_ROI",
                    visible=False,
                    colorMode="field",
                    field={
                        "source": "node",
                        "name": "TI_normal_ROI",
                        "component": "mag",
                    },
                    scale={
                        "kind": "linear",
                        "lo": 0.0,
                        "hi": round(float(normal_max), 6),
                    },
                    threshold=_hide_below(
                        ZERO_EPSILON, round(2.0 * float(normal_max), 6)
                    ),
                )
            )
        nodes = np.asarray(node_coords, dtype=float)
        row = plan.rows[0]
        scene = assemble_scene(
            datasets=[dataset],
            layers=layers,
            cursor=[float(v) for v in row.cursor_ras],
            mm_per_px=float(row.mm_per_px),
            bounds=(
                [float(v) for v in nodes.min(axis=0)],
                [float(v) for v in nodes.max(axis=0)],
            ),
            meta=_meta(meta, plan, field_name, roi_values, lo, hi, "vertices"),
            layout="1+3",
            transparency="peel",
        )
        return _write(out_dir, scene, f"{field_name} in {region_name}")
    except Exception as exc:  # noqa: BLE001 - a scene never fails a job
        logger.warning("analysis scene could not be written: %s", exc)
        return None