Streamlines as tubes#

The 3D view can draw a tractogram’s streamlines as lit tubes instead of lines, which shows better which streamline passes in front of which. The 2D views keep drawing lines. Tubes take far more triangles than lines, so they are drawn only up to a number of streamlines (the tube limit, a setting); above it the 3D view draws lines.

This example builds two crossing bundles of streamlines and saves the 3D view with lines and with tubes, coloured by direction and then in one colour.

import numpy as np

from larmor.types import Tractogram
from larmor.viewer import (
    CameraPreset,
    Category,
    Colouring,
    OptionName,
    TractStyle,
    Viewer,
    ViewKind,
)

Two crossing bundles#

An arch from left to right, and a column from top to bottom through its middle, each streamline shifted at random by a few millimetres (world coordinates in mm).

rng = np.random.default_rng(0)
n, length = 300, 60
t = np.linspace(0, 1, length, dtype=np.float32)


def bundle(curve: np.ndarray, spread: float) -> np.ndarray:
    """n copies of a curve, each shifted at random by about spread mm."""
    shifts = rng.normal(scale=spread, size=(n, 1, 3)).astype(np.float32)
    return curve[None] + shifts


arch = np.stack([80 * t - 40, np.zeros_like(t), 25 * np.sin(np.pi * t)], -1)
column = np.stack([np.zeros_like(t), 4 * np.sin(2 * np.pi * t), 40 - 70 * t], -1)
streamlines = np.concatenate([bundle(arch, 2.5), bundle(column, 2.0)])
points = streamlines.reshape(-1, 3)
offsets = np.arange(0, len(points) + 1, length, dtype=np.int64)
tracts = Tractogram(points, offsets)

viewer = Viewer()
item = viewer.add(tracts, Category.TRACTOGRAMS, name="bundles")
viewer.set_camera(ViewKind.THREE_D, CameraPreset.CORONAL)
viewer.rotate_camera(30.0, 20.0)

Lines, then tubes#

The style is a 3D option: lines by default, or tubes. A tube’s radius is in mm, 0.25 by default; here 0.4, as the bundles are small.

viewer.screenshot(ViewKind.THREE_D, "viewer_tubes_lines.png", size=(600, 600))
viewer.set(item.id, OptionName.STYLE, TractStyle.TUBES)
viewer.set(item.id, OptionName.TUBE_RADIUS, 0.4)
viewer.screenshot(ViewKind.THREE_D, "viewer_tubes_direction.png", size=(600, 600))
Two crossing synthetic bundles drawn as lines in 3D The same bundles drawn as lit tubes, coloured by direction

One colour#

Tubes take the lines’ colouring, opacity and visibility. In one colour, the lighting alone tells the streamlines apart.

viewer.set(item.id, OptionName.COLOURING, Colouring.SINGLE)
viewer.set(item.id, OptionName.COLOUR, "#ffb000")
viewer.screenshot(ViewKind.THREE_D, "viewer_tubes_single.png", size=(600, 600))
print(f"{tracts.num_streamlines} streamlines as tubes of 8 sides")
The same tubes in one amber colour
600 streamlines as tubes of 8 sides