Skip to content

Visualization & Plotting

Ossify supports 2D plotting (via matplotlib) and 3D interactive rendering (via PyVista, install with pip install ossify[viz]).

Overview

Function Category Functions Purpose
Cell Plotting plot_cell_2d, plot_cell_multiview 2D integrated visualization of complete cells
Layer Plotting plot_morphology_2d, plot_annotations_2d, plot_skeleton, plot_points 2D individual layer visualization
Figure Management single_panel_figure, multi_panel_figure, add_scale_bar Layout and annotation utilities
Lineups plot_lineup, plot_lineup_grid, LineupGroup, add_layer_lines Side-by-side cell arrays for comparison
Projections and Rotations Rotation, RotateCell Build custom projection callables
3D Cell Plotting plot_cell_3d 3D integrated visualization of complete cells
3D Layer Plotting plot_morphology_3d, plot_annotations_3d, plot_mesh_3d, plot_graph_3d 3D individual layer visualization

Cell Plotting

plot_cell_2d

plot_cell_2d

plot_cell_2d(cell: Cell, color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, alpha: Optional[Union[str, ndarray, float]] = 1.0, alpha_norm: Optional[Tuple[float, float]] = None, linewidth: Optional[Union[str, ndarray, float]] = 1.0, linewidth_norm: Optional[Tuple[float, float]] = None, widths: Optional[tuple] = (1, 50), root_marker: bool = False, root_size: float = 100.0, root_color: Optional[Union[str, tuple]] = None, synapses: Literal['pre', 'post', 'both', True, False] = False, pre_anno: str = 'pre_syn', pre_color: Optional[Union[str, tuple]] = None, pre_palette: Union[str, dict] = 'coolwarm', pre_color_norm: Optional[Tuple[float, float]] = None, syn_alpha: float = 1, syn_color_scale: Optional[Literal['log']] = None, syn_size: Optional[Union[str, ndarray, float]] = None, syn_size_norm: Optional[Tuple[float, float]] = None, syn_size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, syn_sizes: Optional[ndarray] = (1, 30), post_anno: str = 'post_syn', post_color: Optional[Union[str, tuple]] = None, post_palette: Union[str, dict] = 'coolwarm', post_color_norm: Optional[Tuple[float, float]] = None, projection: Union[str, Callable] = 'xy', rotation_angle: Optional[Union[float, int, Literal['best']]] = None, rotation_axis: Optional[Union[str, ndarray]] = None, offset_h: float = 0.0, offset_v: float = 0.0, invert_y: bool = True, ax: Optional[Axes] = None, units_per_inch: Optional[float] = None, dpi: Optional[float] = None, despine: bool = True, **syn_kwargs) -> Axes

Comprehensive 2D visualization of complete cells with skeleton, annotations, and flexible styling.

Usage Examples

import ossify
import matplotlib.pyplot as plt

# Load cell with skeleton and synapses
cell = ossify.load_cell("neuron.osy")

# Basic plot with compartment coloring
fig, ax = ossify.plot_cell_2d(
    cell,
    color="compartment",  # Color by axon/dendrite
    projection="xy",      # XY projection
    synapses=True         # Show both pre/post synapses
)

# Advanced styling with custom parameters
fig, ax = ossify.plot_cell_2d(
    cell,
    color="strahler_order",           # Hierarchical coloring
    palette="viridis",                # Scientific colormap
    linewidth="radius",               # Width by branch radius
    widths=(1, 20),                   # Min/max linewidth range
    alpha=0.8,                        # Semi-transparent
    root_as_sphere=True,              # Mark root location
    root_size=150,                    # Root marker size
    synapses="pre",                   # Only presynaptic sites
    pre_color="red",                  # Synapse color
    syn_size=30,                      # Synapse marker size
    projection="zy",                  # Side view
    units_per_inch=50000,             # 50,000 nm per inch
    despine=True                      # Clean axes
)
plt.show()

Color and Style Options

# Discrete compartment coloring
plot_cell_2d(cell, color="compartment", palette={"axon": "red", "dendrite": "blue"})

# Continuous property coloring  
plot_cell_2d(cell, color="distance_to_root", palette="plasma", color_norm=(0, 500000))

# Multi-property styling
plot_cell_2d(
    cell,
    color="strahler_order",      # Color by hierarchy
    alpha="confidence",          # Transparency by confidence
    linewidth="thickness",       # Width by morphology
    alpha_norm=(0.3, 1.0),      # Alpha range
    linewidth_norm=(0.5, 5.0),  # Width range
    widths=(2, 40)              # Final width scaling
)

Synapse Visualization

# Separate pre/post synapse styling
plot_cell_2d(
    cell,
    synapses="both",
    pre_anno="pre_syn",           # Presynaptic annotation layer
    pre_color="red",              # Presynaptic color
    pre_palette="Reds",           # If coloring by property
    post_anno="post_syn",         # Postsynaptic annotation layer  
    post_color="blue",            # Postsynaptic color
    post_palette="Blues",         # If coloring by property
    syn_size="activity",          # Size by synapse activity
    syn_sizes=(10, 100),          # Size range
    syn_alpha=0.7                 # Synapse transparency
)

plot_cell_multiview

plot_cell_multiview

plot_cell_multiview(cell: Cell, layout: Literal['stacked', 'side_by_side', 'three_panel'] = 'three_panel', color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, alpha: Optional[Union[str, ndarray, float]] = 1.0, alpha_norm: Optional[Tuple[float, float]] = None, linewidth: Optional[Union[str, ndarray, float]] = 1.0, linewidth_norm: Optional[Tuple[float, float]] = None, widths: Optional[tuple] = (1, 50), root_marker: bool = False, root_size: float = 100.0, root_color: Optional[Union[str, tuple]] = None, synapses: Literal['pre', 'post', 'both', True, False] = False, pre_anno: str = 'pre_syn', pre_color: Optional[Union[str, tuple]] = None, pre_palette: Union[str, dict] = 'coolwarm', pre_color_norm: Optional[Tuple[float, float]] = None, syn_alpha: float = 1, syn_color_scale: Optional[Literal['log']] = None, syn_size: Optional[Union[str, ndarray, float]] = None, syn_size_norm: Optional[Tuple[float, float]] = None, syn_size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, syn_sizes: Optional[ndarray] = (1, 30), post_anno: str = 'post_syn', post_color: Optional[Union[str, tuple]] = None, post_palette: Union[str, dict] = 'coolwarm', post_color_norm: Optional[Tuple[float, float]] = None, invert_y: bool = True, despine: bool = True, units_per_inch: float = 100000, dpi: Optional[float] = None, **syn_kwargs) -> dict

Multi-panel visualization showing different anatomical projections.

Usage Examples

# Three-panel L-shaped layout (default)
fig, axes = ossify.plot_cell_multiview(
    cell,
    layout="three_panel",        # xy (bottom-left), xz (top-left), zy (bottom-right) 
    color="compartment",
    units_per_inch=100000,       # Scale factor
    gap_inches=0.3               # Panel spacing
)

# Side-by-side comparison (xy | zy)
fig, axes = ossify.plot_cell_multiview(
    cell,
    layout="side_by_side",
    color="strahler_order",
    palette="cmc.hawaii",
    synapses=True,
    units_per_inch=75000
)

# Stacked layout (xz over xy)
fig, axes = ossify.plot_cell_multiview(
    cell,
    layout="stacked", 
    color="distance_to_root",
    palette="magma",
    root_as_sphere=True
)

# Access individual axes
xy_ax = axes["xy"]
xz_ax = axes["xz"] 
zy_ax = axes["zy"]

# Add titles or annotations
xy_ax.set_title("Horizontal View (XY)")
zy_ax.set_title("Side View (ZY)")

Layer Plotting

plot_morphology_2d

plot_morphology_2d

plot_morphology_2d(cell: Union[Cell, SkeletonLayer], color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, alpha: Optional[Union[str, ndarray, float]] = 1.0, alpha_norm: Optional[Tuple[float, float]] = None, alpha_extent: Optional[Tuple[float, float]] = None, linewidth: Optional[Union[str, ndarray, float]] = 1.0, linewidth_norm: Optional[Tuple[float, float]] = None, widths: Optional[tuple] = (1, 50), projection: Union[str, Callable] = 'xy', rotation_angle: Optional[Union[float, int, Literal['best']]] = None, rotation_axis: Optional[Union[str, ndarray]] = None, offset_h: float = 0.0, offset_v: float = 0.0, root_marker: bool = False, root_size: float = 100.0, root_color: Optional[Union[str, tuple]] = None, zorder: int = 2, invert_y: bool = True, ax: Optional[Axes] = None) -> Axes

Plot 2D skeleton with flexible styling options.

Parameters:

  • cell (Cell or SkeletonLayer) –

    Cell or SkeletonLayer to plot

  • projection (str or Callable, default: "xy" ) –

    Projection function or string mapping 3d points to a 2d projection.

  • color (str, np.ndarray, or tuple, default: None ) –

    Color specification - can be feature name, array of values, or matplotlib color

  • palette (str or dict, default: "coolwarm" ) –

    Colormap for mapping array values to colors

  • color_norm (tuple of float, default: None ) –

    (min, max) tuple for color normalization, in the original (pre-transform) value space.

  • color_scale (log, default: "log" ) –

    Value transform applied before colormap projection. "log" log-transforms feature values so the colormap is distributed linearly in log-space. color_norm bounds remain in original units and are converted internally. Mirrors :func:plot_morphology_3d.

  • alpha (str, np.ndarray, or float, default: 1.0 ) –

    Alpha specification - can be feature name, array, or single value. Feature names / arrays are rescaled to alpha_extent.

  • alpha_norm (tuple of float, default: None ) –

    (min, max) clip range for alpha values in original feature units.

  • alpha_extent (tuple of float, default: None ) –

    (min, max) output range for rescaled alpha values. Default (0.0, 1.0) — i.e., the dimmest vertex is fully transparent. Pass e.g. (0.1, 1.0) to keep low-end vertices faintly visible.

  • linewidth (str, np.ndarray, or float, default: 1.0 ) –

    Linewidth specification - can be feature name, array, or single value

  • linewidth_norm (tuple of float, default: None ) –

    (min, max) tuple for linewidth normalization

  • widths (tuple, default: (1, 50) ) –

    (min, max) tuple for final linewidth scaling

  • ax (Axes, default: None ) –

    Matplotlib axes

Returns:

  • Axes

    Matplotlib axes with skeleton plotted

Flexible 2D plotting of skeleton morphology with advanced styling.

Usage Examples

# Basic skeleton plotting
ax = ossify.plot_morphology_2d(
    cell.skeleton,           # Can use Cell or SkeletonLayer
    projection="xy"
)

# Advanced styling
ax = ossify.plot_morphology_2d(
    cell,
    color="branch_order",        # Color by topological property
    palette="Set1",              # Discrete colormap
    alpha="confidence",          # Transparency by data quality
    linewidth="diameter",        # Width by branch diameter  
    linewidth_norm=(0.1, 10.0), # Diameter range (μm)
    widths=(0.5, 25),           # Plot width range (points)
    projection="xz",             # Sagittal view
    offset_h=1000,              # Horizontal offset (nm)
    offset_v=500,               # Vertical offset (nm) 
    zorder=3,                   # Drawing order
    invert_y=True,              # Invert Y axis
    ax=existing_axis            # Plot on existing axes
)

plot_annotations_2d

plot_annotations_2d

plot_annotations_2d(annotation: PointCloudLayer, color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, alpha: float = 1, size: Optional[Union[str, ndarray, float]] = None, size_norm: Optional[Tuple[float, float]] = None, size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, sizes: Optional[ndarray] = (1, 30), projection: Union[str, Callable] = 'xy', rotation_angle: Optional[Union[float, int, Literal['best']]] = None, rotation_axis: Optional[Union[str, ndarray]] = None, rotation_center: Optional[ndarray] = None, offset_h: float = 0.0, offset_v: float = 0.0, invert_y: bool = True, ax: Optional[Axes] = None, **kwargs) -> Axes

Plot a 2D scatter of a :class:PointCloudLayer annotation.

Parameters:

  • annotation (PointCloudLayer) –

    Annotation layer to plot. Raw np.ndarray is also accepted, but feature-name resolution then requires the layer.

  • color (str, np.ndarray, or tuple, default: None ) –

    Color specification. A string matching a feature name resolves to a per-point value array mapped through palette. Otherwise treated as a matplotlib color.

  • palette (str or dict, default: "coolwarm" ) –

    Colormap or dict for scalar color mapping.

  • color_norm (tuple of float, default: None ) –

    (min, max) clip range, in original (pre-transform) units.

  • color_scale (log, default: "log" ) –

    Value transform applied before colormap projection. Mirrors :func:plot_annotations_3d.

  • alpha (float, default: 1 ) –

    Marker opacity.

  • size (str, np.ndarray, or float, default: None ) –

    Marker size specification. Feature name, per-point array, or scalar.

  • size_norm (tuple of float, default: None ) –

    (min, max) clip range for size mapping, in original units.

  • size_scale ((log, sqrt, cbrt), default: "log" ) –

    Value transform applied before size normalization. "sqrt" is useful when the feature is a cross-sectional area; "cbrt" when the feature is a volume. Mirrors :func:plot_annotations_3d.

  • sizes (tuple of float, default: (1, 30) ) –

    (min_size, max_size) output range for size rescaling. Default (1, 30).

Returns:

  • Axes

    Matplotlib axes with the annotation rendered.

Scatter plots for point cloud annotations with flexible styling.

Usage Examples

# Basic annotation plotting
ax = ossify.plot_annotations_2d(
    cell.annotations["pre_syn"],
    projection="xy"
)

# Advanced styling
ax = ossify.plot_annotations_2d(
    cell.annotations["synapses"],
    color="activity_level",      # Color by annotation property
    palette="plasma",            # Continuous colormap
    color_norm=(0, 1),          # Value range for coloring
    size="strength",            # Size by property
    size_norm=(0.1, 2.0),       # Property range
    sizes=(5, 80),              # Marker size range (points²)
    alpha=0.8,                  # Transparency
    projection="zy",            # Side projection
    offset_h=500,               # Position offset
    offset_v=0,
    ax=ax,                      # Add to existing plot
    edgecolors='black',         # Marker edge color
    linewidths=0.5              # Edge width
)

plot_skeleton & plot_points

Lower-level functions for precise control:

plot_skeleton

plot_skeleton(skel: SkeletonLayer, projection: Union[str, Callable] = 'xy', rotation_angle: Optional[Union[float, int, Literal['best']]] = None, rotation_axis: Optional[Union[str, ndarray]] = None, colors: Optional[ndarray] = None, alpha: Optional[ndarray] = None, linewidths: Optional[ndarray] = None, offset_h: float = 0.0, offset_v: float = 0.0, zorder: int = 2, invert_y: bool = True, ax: Optional[Axes] = None) -> Axes

Plot skeleton with explicit arrays for styling.

Parameters:

  • skel (SkeletonLayer) –

    SkeletonLayer to plot

  • projection (str or Callable, default: "xy" ) –

    Projection function or string

  • colors (ndarray, default: None ) –

    (N, 3) or (N, 4) RGB/RGBA color array for vertices

  • alpha (ndarray, default: None ) –

    (N,) alpha values for vertices

  • linewidths (ndarray, default: None ) –

    (N,) linewidth values for vertices

  • offset_h (float, default: 0.0 ) –

    Horizontal offset for projection

  • offset_v (float, default: 0.0 ) –

    Vertical offset for projection

  • zorder (int, default: 2 ) –

    Drawing order for line collection

  • invert_y (bool, default: True ) –

    Whether to automatically invert y-axis for projections containing 'y'

  • ax (Axes, default: None ) –

    Matplotlib axes

Returns:

  • Axes

    Matplotlib axes with skeleton plotted

plot_points

plot_points(points: ndarray, sizes: Optional[ndarray] = None, colors: Optional[ndarray] = None, palette: Union[str, Dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, projection: Union[str, Callable] = 'xy', rotation_angle: Optional[Union[float, int, Literal['best']]] = None, rotation_axis: Optional[Union[str, ndarray]] = None, rotation_center: Optional[ndarray] = None, offset_h: float = 0.0, offset_v: float = 0.0, invert_y: bool = True, ax: Optional[Axes] = None, zorder: int = 2, **scatter_kws) -> Axes

Direct Array Styling

# Direct control with pre-computed arrays
skeleton = cell.skeleton

# Prepare styling arrays
colors = ossify.strahler_number(skeleton)  # Color values
alphas = np.ones(skeleton.n_vertices) * 0.8  # Uniform transparency  
widths = skeleton.get_feature("radius") * 2    # Width by radius

# Plot with explicit arrays
ax = ossify.plot_skeleton(
    skeleton,
    projection="xy",
    colors=colors,              # (N, 3) RGB or (N, 4) RGBA array
    alpha=alphas,               # (N,) alpha values  
    linewidths=widths,          # (N,) linewidth values
    zorder=2
)

# Plot points with arrays
points = cell.annotations["pre_syn"].vertices
sizes = np.random.uniform(10, 50, len(points))
colors = np.random.choice(['red', 'blue'], len(points))

ax = ossify.plot_points(
    points=points,
    sizes=sizes,
    colors=colors,
    projection="xy",
    ax=ax,
    zorder=3
)

Figure Management

single_panel_figure

single_panel_figure

single_panel_figure(data_bounds_min: ndarray, data_bounds_max: ndarray, units_per_inch: float, despine: bool = True, dpi: Optional[float] = None) -> Tuple[Figure, Axes]

Create a single panel figure with precise unit-based sizing.

Parameters:

  • data_bounds_min (ndarray) –

    2-element array [x_min, y_min] of data bounds

  • data_bounds_max (ndarray) –

    2-element array [x_max, y_max] of data bounds

  • units_per_inch (float) –

    Number of data units per inch for scaling

  • despine (bool, default: True ) –

    Whether to remove axis spines and ticks for clean appearance

  • dpi (float, default: None ) –

    Dots per inch for figure resolution. If None, uses matplotlib default.

Returns:

  • tuple of (plt.Figure, plt.Axes)

    Figure and axes objects with correct unit scaling

Examples:

>>> bounds_min = np.array([0, 0])
>>> bounds_max = np.array([100, 50])
>>> fig, ax = create_single_panel_figure(bounds_min, bounds_max, 10)
>>> # Creates 10" x 5" figure with 10 units per inch

Create figures with precise unit-based sizing for consistent scaling.

Usage Examples

# Calculate data bounds
bounds_min = cell.skeleton.bbox[0]  # [x_min, y_min, z_min]
bounds_max = cell.skeleton.bbox[1]  # [x_max, y_max, z_max]

# Create figure with specific scale
fig, ax = ossify.single_panel_figure(
    data_bounds_min=bounds_min[:2],    # [x_min, y_min] 
    data_bounds_max=bounds_max[:2],    # [x_max, y_max]
    units_per_inch=50000,              # 50,000 nm per inch
    despine=True,                      # Remove axes
    dpi=300                            # High resolution
)

# Plot data on precisely-sized axes
ossify.plot_morphology_2d(cell, ax=ax)

# The figure size automatically matches data aspect ratio
print(f"Figure size: {fig.get_size_inches()}")

multi_panel_figure

multi_panel_figure

multi_panel_figure(data_bounds_min: ndarray, data_bounds_max: ndarray, units_per_inch: float, layout: Literal['side_by_side', 'stacked', 'three_panel'], gap_inches: float = 0.5, despine: bool = True, dpi: Optional[float] = None) -> Tuple[Figure, Dict[str, Axes]]

Create multi-panel figure with precise unit-based sizing and alignment.

Parameters:

  • data_bounds_min (ndarray) –

    3-element array [x_min, y_min, z_min] of data bounds

  • data_bounds_max (ndarray) –

    3-element array [x_max, y_max, z_max] of data bounds

  • units_per_inch (float) –

    Number of data units per inch for scaling

  • layout ((side_by_side, stacked, three_panel), default: "side_by_side" ) –

    Layout configuration: - "side_by_side": xy | zy (horizontal) - "stacked": xz over xy (vertical) - "three_panel": L-shaped (xy bottom-left, xz top-left, zy bottom-right)

  • gap_inches (float, default: 0.5 ) –

    Gap between panels in inches

  • despine (bool, default: True ) –

    Whether to remove axis spines and ticks for clean appearance

  • dpi (float, default: None ) –

    Dots per inch for figure resolution. If None, uses matplotlib default.

Returns:

  • tuple of (plt.Figure, dict of plt.Axes)

    Figure and dictionary of axes keyed by projection. - "side_by_side": {"xy": xy_ax, "zy": zy_ax} - "stacked": {"xz": xz_ax, "xy": xy_ax} - "three_panel": {"xy": xy_ax, "xz": xz_ax, "zy": zy_ax}

Examples:

>>> bounds_min = np.array([0, 0, 0])
>>> bounds_max = np.array([100, 50, 75])
>>> fig, axes_dict = create_multi_panel_figure(bounds_min, bounds_max, 10, "side_by_side")
>>> xy_ax, zy_ax = axes_dict["xy"], axes_dict["zy"]

Create aligned multi-panel layouts with consistent scaling.

Usage Examples

# Three-panel layout with precise alignment
fig, axes = ossify.multi_panel_figure(
    data_bounds_min=cell.skeleton.bbox[0],
    data_bounds_max=cell.skeleton.bbox[1], 
    units_per_inch=75000,
    layout="three_panel",
    gap_inches=0.4,                    # Panel spacing
    despine=True,
    dpi=300
)

# Plot different projections
for projection, ax in axes.items():
    ossify.plot_morphology_2d(
        cell,
        projection=projection,
        color="compartment", 
        ax=ax
    )
    ax.set_title(f"{projection.upper()} View")

plt.suptitle("Multi-View Neuron Analysis")

add_scale_bar

add_scale_bar

add_scale_bar(ax: Axes, length: float, position: Tuple[float, float] = (0.05, 0.05), color: str = 'black', linewidth: float = 10.0, orientation: Literal['h', 'v', 'horizontal', 'vertical'] = 'h', feature: Optional[str] = None, feature_offset: float = 0.01, fontsize: float = 10) -> None

Add a scale bar to an axis with precise positioning.

Parameters:

  • ax (Axes) –

    Matplotlib axes to add scale bar to

  • length (float) –

    Length of scale bar in data units

  • position (tuple of float, default: (0.05, 0.05) ) –

    Starting position as fraction of axis dimensions (x_frac, y_frac). (0, 0) is bottom-left, (1, 1) is top-right.

  • color (str, default: "black" ) –

    Color of the scale bar line

  • linewidth (float, default: 3.0 ) –

    Width of the scale bar line in points

  • feature (str, default: None ) –

    Text feature for the scale bar (e.g., "100 μm")

  • feature_offset (float, default: 0.01 ) –

    Vertical offset for feature as fraction of axis height

  • fontsize (float, default: 10 ) –

    Font size for scale bar feature

Examples:

>>> fig, ax = plt.subplots()
>>> ax.plot([0, 100], [0, 50])
>>> add_scale_bar(ax, length=20, position=(0.1, 0.1), feature="20 units")
>>> # Add scale bar to morphology plot
>>> fig, ax = single_panel_figure(bounds_min, bounds_max, 10)
>>> plot_skeleton(skeleton, ax=ax)
>>> add_scale_bar(ax, length=50, position=(0.8, 0.05), feature="50 μm")

Add calibrated scale bars to plots.

Usage Examples

# Add scale bars to plots
fig, ax = ossify.single_panel_figure(
    bounds_min, bounds_max, 
    units_per_inch=100000  # 100,000 nm per inch
)

ossify.plot_morphology_2d(cell, ax=ax)

# Horizontal scale bar (bottom-left)
ossify.add_scale_bar(
    ax,
    length=50000,                      # 50 μm in nm
    position=(0.05, 0.05),            # Fractional position
    feature="50 μm",                    # Text feature
    color="black",
    linewidth=8,
    fontsize=12
)

# Vertical scale bar (top-right)
ossify.add_scale_bar(
    ax,
    length=25000,                     # 25 μm 
    position=(0.9, 0.7),
    orientation="vertical",           # or "v"
    feature="25 μm",
    feature_offset=0.02,               # feature spacing
    color="white",                   # For dark backgrounds
    linewidth=6
)

Advanced Plotting Workflows

Publication Figure Pipeline

def create_publication_figure(cell, output_path="figure.pdf"):
    \"\"\"Create a publication-ready multi-panel figure\"\"\"

    # Calculate compartmentalization
    is_axon = ossify.label_axon_from_synapse_flow(cell)
    compartment = np.where(is_axon, "Axon", "Dendrite")
    cell.skeleton.add_feature(compartment, "compartment")

    # Create multi-panel layout
    fig, axes = ossify.plot_cell_multiview(
        cell,
        layout="three_panel",
        color="compartment",
        palette={"Axon": "#d62728", "Dendrite": "#1f77b4"},  # Red/Blue
        linewidth="radius",
        widths=(1, 15),
        synapses="both", 
        pre_color="#ff7f0e",       # Orange presynaptic
        post_color="#2ca02c",      # Green postsynaptic
        syn_size=25,
        units_per_inch=100000,     # 100k nm/inch
        gap_inches=0.3,
        despine=True,
        dpi=300
    )

    # Add scale bars
    for proj, ax in axes.items():
        if proj == "xy":
            ossify.add_scale_bar(ax, 100000, (0.05, 0.05), "100 μm", fontsize=14)
        elif proj == "xz":  
            ossify.add_scale_bar(ax, 50000, (0.05, 0.05), "50 μm", fontsize=12)
        elif proj == "zy":
            ossify.add_scale_bar(ax, 75000, (0.05, 0.05), "75 μm", fontsize=12)

    # Add panel features
    axes["xy"].text(0.02, 0.98, "A", transform=axes["xy"].transAxes, 
                   fontsize=20, fontweight='bold', va='top')
    axes["xz"].text(0.02, 0.98, "B", transform=axes["xz"].transAxes,
                   fontsize=20, fontweight='bold', va='top')
    axes["zy"].text(0.02, 0.98, "C", transform=axes["zy"].transAxes,
                   fontsize=20, fontweight='bold', va='top')

    # Add figure title and save
    fig.suptitle("Neuronal Compartmentalization Analysis", fontsize=16, y=0.95)
    plt.tight_layout()
    plt.savefig(output_path, dpi=300, bbox_inches='tight')

    return fig

# Generate publication figure
fig = create_publication_figure(cell, "neuron_analysis.pdf")

Interactive Analysis Workflow

def plot_analysis_comparison(cell, algorithms=['flow', 'spectral']):
    \"\"\"Compare different analysis algorithms visually\"\"\"

    results = {}

    # Run different algorithms
    if 'flow' in algorithms:
        results['flow'] = ossify.label_axon_from_synapse_flow(cell)

    if 'spectral' in algorithms:
        results['spectral'] = ossify.label_axon_from_spectral_split(cell)

    # Create comparison plot
    n_methods = len(results)
    fig, axes = plt.subplots(1, n_methods, figsize=(6*n_methods, 6))
    if n_methods == 1:
        axes = [axes]

    for i, (method, is_axon) in enumerate(results.items()):
        compartment = np.where(is_axon, "Axon", "Dendrite")

        ossify.plot_morphology_2d(
            cell,
            color=compartment,
            palette={"Axon": "red", "Dendrite": "blue"},
            ax=axes[i]
        )

        axes[i].set_title(f"{method.capitalize()} Method")
        axes[i].set_aspect('equal')

    plt.tight_layout()
    return fig, results

# Compare methods
fig, results = plot_analysis_comparison(cell)

# Calculate agreement
if len(results) > 1:
    methods = list(results.keys())
    agreement = (results[methods[0]] == results[methods[1]]).mean()
    print(f"Agreement between methods: {agreement:.1%}")

Custom Styling Functions

def create_custom_colormap():
    \"\"\"Create custom colormap for morphological data\"\"\"
    from matplotlib.colors import LinearSegmentedColormap

    colors = ['#440154', '#31688e', '#35b779', '#fde725']  # Viridis-like
    n_bins = 256
    cmap = LinearSegmentedColormap.from_list('custom', colors, N=n_bins)
    return cmap

def style_by_distance_to_branch(cell, ax):
    \"\"\"Style skeleton by distance to nearest branch point\"\"\"
    skeleton = cell.skeleton
    branch_points = skeleton.branch_points

    # Calculate distance to nearest branch for each vertex
    distances = []
    for vertex in skeleton.vertex_index:
        dist_to_branches = skeleton.distance_between(
            [vertex], branch_points, limit=100000
        )
        distances.append(dist_to_branches.min() if len(dist_to_branches) > 0 else np.inf)

    distances = np.array(distances)

    # Plot with custom styling
    ossify.plot_morphology_2d(
        cell,
        color=distances,
        palette=create_custom_colormap(),
        color_norm=(0, np.percentile(distances[distances < np.inf], 95)),
        linewidth=2,
        ax=ax
    )

    return distances

# Apply custom styling
fig, ax = plt.subplots(figsize=(10, 8))
distances = style_by_distance_to_branch(cell, ax)
ax.set_title("Distance to Nearest Branch Point")

Plotting Design Principles

Unit Consistency: All plotting functions support precise unit control for consistent scaling across figures.

Flexible Styling: Multiple ways to specify colors, sizes, and transparency - from simple constants to complex property mappings.

Layer Integration: Seamless integration between different data layers in combined plots.

Publication Ready: Built-in support for high-DPI output, precise scaling, and clean styling.

Best Practices

  • Scale Bars: Always include scale bars for spatial data
  • Color Schemes: Use perceptually uniform colormaps (viridis, plasma, cividis) for continuous data
  • Resolution: Set dpi=300 for publication figures
  • Aspect Ratios: Use units_per_inch for consistent scaling across different data sizes
  • Layering: Use zorder to control drawing order when combining multiple plot elements

Lineups

Lineups arrange multiple cells side-by-side on a single axis — useful for comparing morphologies across a small group, or for figure panels that show several example cells. See the visualization guide for a walkthrough.

plot_lineup

plot_lineup

plot_lineup(cells: List[Cell], projection: Union[str, Callable] = 'xy', color: Optional[Union[str, ndarray, tuple, List]] = None, palette: Union[str, dict, List[Union[str, dict]]] = 'coolwarm', color_norm: Optional[Union[Tuple[float, float], List]] = None, alpha: Optional[Union[str, ndarray, float, List]] = 1.0, alpha_norm: Optional[Union[Tuple[float, float], List]] = None, alpha_extent: Optional[Union[Tuple[float, float], List]] = None, linewidth: Optional[Union[str, ndarray, float, List]] = 1.0, linewidth_norm: Optional[Union[Tuple[float, float], List]] = None, widths: Optional[Union[tuple, List]] = (1, 50), root_marker: Union[bool, List[bool]] = False, root_size: Union[float, List[float]] = 100.0, root_color: Optional[Union[str, tuple, List]] = None, gap: float = 0.0, align: Literal['natural', 'soma', 'point'] = 'natural', alignment_point: Optional[Union[ndarray, List[ndarray]]] = None, invert_y: bool = True, ax: Optional[Axes] = None, units_per_inch: Optional[float] = None, dpi: Optional[float] = None, despine: bool = True) -> Axes

Plot multiple cells side-by-side in a single figure.

Parameters:

  • cells (List[Cell]) –

    Cells to plot.

  • projection (str or Callable, default: "xy" ) –

    Shared projection for all cells.

  • color (str, np.ndarray, tuple, or List, default: None ) –

    Per-cell or shared color specification.

  • palette (str, dict, or List, default: "coolwarm" ) –

    Per-cell or shared colormap.

  • color_norm (tuple or List, default: None ) –

    Per-cell or shared (min, max) color normalization.

  • alpha (str, np.ndarray, float, or List, default: 1.0 ) –

    Per-cell or shared alpha specification.

  • alpha_norm (tuple or List, default: None ) –

    Per-cell or shared alpha normalization range.

  • alpha_extent (tuple or List, default: None ) –

    Per-cell or shared alpha output range.

  • linewidth (str, np.ndarray, float, or List, default: 1.0 ) –

    Per-cell or shared linewidth specification.

  • linewidth_norm (tuple or List, default: None ) –

    Per-cell or shared linewidth normalization.

  • widths (tuple or List, default: (1, 50) ) –

    Per-cell or shared (min, max) linewidth scaling.

  • root_marker (bool or List[bool], default: False ) –

    Per-cell or shared root marker flag.

  • root_size (float or List[float], default: 100.0 ) –

    Per-cell or shared root marker size.

  • root_color (str, tuple, or List, default: None ) –

    Per-cell or shared root marker color.

  • gap (float, default: 0.0 ) –

    Horizontal gap between cells.

  • align ("natural", "soma", or "point", default: "natural" ) –

    Vertical alignment mode.

  • alignment_point (ndarray or List[ndarray], default: None ) –

    Required when align="point". A single 3D point (broadcast) or one per cell.

  • invert_y (bool, default: True ) –

    Whether to invert the y axis.

  • ax (Axes, default: None ) –

    Existing axes to plot onto. If None, a new figure is created.

  • units_per_inch (float, default: None ) –

    Data units per inch for auto-sizing the figure. Used only when ax=None.

  • dpi (float, default: None ) –

    Figure DPI. Used only when ax=None and units_per_inch is given.

  • despine (bool, default: True ) –

    Whether to remove spines when creating a new figure.

Returns:

  • Axes

    Axes with all cells plotted.

Raises:

  • ValueError

    If cells is empty, if a list param has the wrong length, or if align="point" but alignment_point is None.

plot_lineup_grid

plot_lineup_grid

plot_lineup_grid(groups: List[LineupGroup], *, projection: Union[str, Callable] = 'xy', align: Literal['natural', 'soma', 'point'] = 'natural', inter_cell_gap: float = 0.0, inter_group_gap: float = 0.0, row_max_cells: Optional[int] = None, row_max_width: Optional[float] = None, row_gap: float = 0.0, layer_lines: Optional[Dict[float, Optional[str]]] = None, layer_line_kwargs: Optional[dict] = None, group_label_offset: float = 0.0, group_label_kwargs: Optional[dict] = None, alignment_points: Optional[List[List[ndarray]]] = None, invert_y: bool = True, units_per_inch: Optional[float] = None, dpi: Optional[float] = None, despine: bool = True, ax: Optional[Axes] = None) -> Axes

Plot a grid of cell groups with per-group styling and multi-row layout.

Each group's cells are placed contiguously, with optional gaps between cells and between groups. When row_max_cells or row_max_width is set, groups wrap to new rows as units — a single group is never split across rows. Group labels float above each group's plotted cells (locally per group, not globally per row). Optional layer guide lines can be drawn across the full plot.

Parameters:

  • groups (list of LineupGroup) –

    Groups of cells with shared styling. Build these by spreading named style dicts into the constructor; see :class:LineupGroup for examples.

  • projection (str or Callable, default: "xy" ) –

    Shared projection for all cells.

  • align ((natural, soma, point), default: "natural" ) –

    Vertical alignment mode. "natural" preserves each cell's y coordinate (anatomical depth); "soma" aligns each cell's soma to the row's reference y; "point" aligns the per-cell alignment_points to the row reference.

  • inter_cell_gap (float, default: 0.0 ) –

    Horizontal spacing between adjacent cells within a group, in data units.

  • inter_group_gap (float, default: 0.0 ) –

    Extra horizontal spacing between adjacent groups, in data units.

  • row_max_cells (int, default: None ) –

    Wrap to a new row before the running cell count exceeds this.

  • row_max_width (float, default: None ) –

    Wrap to a new row before the running row width (data units) exceeds this. When both row_max_cells and row_max_width are given, whichever fires first triggers the wrap.

  • row_gap (float, default: 0.0 ) –

    Vertical spacing between rows, in data units.

  • layer_lines (dict of float -> str or None, default: None ) –

    {y: label} mapping passed to :func:add_layer_lines. Pass None as a value to draw the line without a label.

  • layer_line_kwargs (dict, default: None ) –

    Extra kwargs forwarded to :func:add_layer_lines.

  • group_label_offset (float, default: 0.0 ) –

    Vertical offset from each group's projected top edge to its label, in data units. Direction is "above on screen": for y-inverted plots (the default) the label sits at min_plotted_y - group_label_offset; otherwise at max_plotted_y + group_label_offset.

  • group_label_kwargs (dict, default: None ) –

    Extra keyword arguments forwarded to :meth:Axes.text for the group labels (e.g. fontsize, color, fontweight).

  • alignment_points (list of list of np.ndarray, default: None ) –

    Required when align="point". alignment_points[gi][ci] is the 3D anchor point for cell ci in group gi.

  • invert_y (bool, default: True ) –

    Invert the y-axis for string projections containing "y". Mirrors the behavior of other plot functions.

  • units_per_inch (optional, default: None ) –

    Passed to :func:single_panel_figure when ax is None and units_per_inch is given.

  • dpi (optional, default: None ) –

    Passed to :func:single_panel_figure when ax is None and units_per_inch is given.

  • despine (optional, default: None ) –

    Passed to :func:single_panel_figure when ax is None and units_per_inch is given.

  • ax (Axes, default: None ) –

    Existing axes to render into. A new figure is created when None.

Returns:

  • Axes

    Axes with all cells, layer lines, and group labels drawn.

LineupGroup

LineupGroup dataclass

LineupGroup(cells: List[Cell], label: Optional[str] = None, rotation_angle: Optional[Union[float, int, Literal['best'], List]] = None, rotation_axis: Optional[Union[str, ndarray, List]] = None, color: Optional[Union[str, ndarray, tuple, List]] = None, palette: Union[str, dict, List] = 'coolwarm', color_norm: Optional[Union[Tuple[float, float], List]] = None, color_scale: Optional[Union[Literal['log'], List]] = None, alpha: Union[str, ndarray, float, List] = 1.0, alpha_norm: Optional[Union[Tuple[float, float], List]] = None, alpha_extent: Optional[Union[Tuple[float, float], List]] = None, linewidth: Union[str, ndarray, float, List] = 1.0, linewidth_norm: Optional[Union[Tuple[float, float], List]] = None, widths: Optional[Union[tuple, List]] = (1, 50), root_marker: Union[bool, List[bool]] = False, root_size: Union[float, List[float]] = 100.0, root_color: Optional[Union[str, tuple, List]] = None, synapses: Literal['pre', 'post', 'both', True, False] = False, pre_anno: str = 'pre_syn', pre_color: Optional[Union[str, tuple]] = None, pre_palette: Union[str, dict] = 'coolwarm', pre_color_norm: Optional[Tuple[float, float]] = None, post_anno: str = 'post_syn', post_color: Optional[Union[str, tuple]] = None, post_palette: Union[str, dict] = 'coolwarm', post_color_norm: Optional[Tuple[float, float]] = None, syn_alpha: float = 1.0, syn_color_scale: Optional[Literal['log']] = None, syn_size: Optional[Union[str, ndarray, float]] = None, syn_size_norm: Optional[Tuple[float, float]] = None, syn_size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, syn_sizes: Optional[ndarray] = (1, 30), extra_kwargs: Optional[Dict[str, Any]] = dict())

A labeled bag of cells with shared styling, used by :func:plot_lineup_grid.

Each style field mirrors the matching keyword on :func:plot_cell_2d. Per-cell fields (color, palette, alpha, linewidth, the root markers, etc.) accept either a scalar (broadcast across all cells in the group) or a list with one entry per cell. Synapse-related fields are uniform across the group.

Parameters:

  • cells (list of Cell) –

    The cells in this group.

  • label (str, default: None ) –

    Title displayed above the group's cells. None suppresses it.

Examples:

Define named styles once, then build groups:

>>> L2A = dict(color="compartment", palette={SWC_AXON: "tab:blue",
...                                          SWC_DENDRITE: "navy"})
>>> L2B = dict(color="compartment", palette={SWC_AXON: "lightblue",
...                                          SWC_DENDRITE: "steelblue"})
>>> groups = [
...     LineupGroup(l2a_cells, label="L2a", **L2A),
...     LineupGroup(l2b_cells, label="L2b", **L2B),
... ]
>>> plot_lineup_grid(groups=groups, row_max_width=2000,
...                  layer_lines={0: "L1", 250: "L2/3", 500: "L4"})

Mix styles within a single group via per-cell broadcasts:

>>> LineupGroup(cells, label="Comparison",
...             color=["compartment"] * len(cells),
...             palette=[L2A["palette"], L3A["palette"], L2A["palette"]],
...             alpha=[1.0, 1.0, 0.3])

add_layer_lines

add_layer_lines

add_layer_lines(ax: Axes, layer_lines: Dict[float, Optional[str]], color: str = 'gray', linestyle: str = '--', linewidth: float = 0.5, label_fontsize: float = 9.0, label_pad: float = 0.01, label_kwargs: Optional[dict] = None, line_kwargs: Optional[dict] = None) -> Axes

Add horizontal layer reference lines with optional left-margin labels.

Parameters:

  • ax (Axes) –

    Axes to annotate.

  • layer_lines (dict of float -> str or None) –

    Mapping from y-coordinate (in data units) to a label string. Pass None as the value to draw the line without a label.

  • color (str, default: "gray" ) –

    Color for both lines and labels.

  • linestyle (str, default: "--" ) –

    Line style for the reference lines.

  • linewidth (float, default: 0.5 ) –

    Width of the reference lines in points.

  • label_fontsize (float, default: 9.0 ) –

    Font size for labels.

  • label_pad (float, default: 0.01 ) –

    Horizontal pad between the axis edge and each label, expressed as a fraction of the axes width.

  • label_kwargs (dict, default: None ) –

    Extra keyword arguments forwarded to :meth:Axes.text for the labels. Overrides any of the defaults above.

  • line_kwargs (dict, default: None ) –

    Extra keyword arguments forwarded to :meth:Axes.axhline. Overrides any of the defaults above.

Returns:

  • Axes

    The same axes, with lines and labels added.

Examples:

>>> add_layer_lines(ax, {0: "L1", 250: "L2/3", 500: "L4",
...                      800: "L5", 1100: "L6"})

Projections and Rotations

The projection keyword of every 2D plotting function accepts either a string label (e.g. "xy") or a callable mapping (N, 3) → (N, 2). Rotation and RotateCell are factories that build such callables for common rotated-view use cases. See the visualization guide for examples.

Rotation

Rotation

Rotation(center: ndarray, axis: Union[ndarray, Literal['x', 'y', 'z']], angle: float, new_center: Optional[ndarray] = None, invert_y: bool = True) -> Callable[[ndarray], ndarray]

Return a projection callable that rotates 3D points and projects to 2D.

The callable accepts an (N, 3) array of 3D points, applies a rotation about the given axis and center, projects to 2D (xy plane), optionally inverts y, and optionally translates the center to a new 2D location.

Compatible with the projection parameter of all ossify plotting functions.

Parameters:

  • center (ndarray) –

    3D point about which the rotation is performed, shape (3,). The rotation center is a fixed point of the transform.

  • axis (ndarray or x | y | z) –

    Rotation axis. String labels are converted to unit vectors.

  • angle (float) –

    Rotation angle in degrees.

  • new_center (ndarray, default: None ) –

    If provided, shifts the 2D output so that the projected rotation center appears at this 2D location. Shape (2,). Useful for centering a cell at the origin ([0, 0]) or at a specific layout position.

  • invert_y (bool, default: True ) –

    If True, negates the y coordinate of the projected output, matching the image-space convention (y increases downward) used by the standard string projections.

Returns:

Examples:

Rotate 90° about the z-axis, centered at the soma:

>>> proj = Rotation(soma_xyz, "z", 90)
>>> plot.plot_skeleton(skel, projection=proj)

Center the projected cell at the plot origin:

>>> proj = Rotation(soma_xyz, "z", 90, new_center=np.array([0.0, 0.0]))

RotateCell

RotateCell

RotateCell(cell: Cell, axis: Union[ndarray, Literal['x', 'y', 'z'], Literal['best'], None] = None, angle: Union[float, Literal['best'], None] = None, center: Optional[ndarray] = None, new_center: Optional[ndarray] = None, invert_y: bool = True) -> Callable[[ndarray], ndarray]

Return a projection callable for a cell, with automatic center and PCA modes.

A high-level wrapper around :func:Rotation that extracts the rotation center from the cell's soma location and supports PCA-based automatic angle optimization.

Parameters:

  • cell (Cell) –

    The cell to build a projection for. Used to extract the default rotation center and skeleton vertices for PCA.

  • axis (ndarray or x | y | z or best or None, default: None ) –

    Rotation axis. String labels "x"/"y"/"z" are converted to unit vectors. "best" or None triggers full PCA: the minimum- variance axis of the skeleton is used (see Notes).

  • angle (float or best or None, default: None ) –

    Rotation angle in degrees, or "best" to find the optimal angle about the given axis via PCA. None is treated as 0.

  • center (ndarray, default: None ) –

    3D rotation center. Defaults to cell.skeleton.root_location.

  • new_center (ndarray, default: None ) –

    2D display position for the rotation center after projection. Passed through to :func:Rotation.

  • invert_y (bool, default: True ) –

    Passed through to :func:Rotation.

Returns:

Notes

Both PCA modes share the same two-step algorithm via _best_angle_for_axis:

  • Axis given, angle="best": project skeleton vertices onto the plane perpendicular to the given axis, run 2D PCA, and return the angle that aligns the principal axis with x.
  • axis="best" or None: run 3D PCA first to find the minimum-variance direction (PC3), use that as the rotation axis, then apply the same constrained angle-finding step.

Examples:

Rotate about y to the best orientation:

>>> proj = RotateCell(cell, axis="y", angle="best")
>>> plot.plot_cell_2d(cell, projection=proj)

Fully automatic best view:

>>> proj = RotateCell(cell)
>>> plot.plot_cell_2d(cell, projection=proj)

3D Cell Plotting

Installation

3D plotting requires PyVista. Install with pip install ossify[viz].

plot_cell_3d

plot_cell_3d

plot_cell_3d(cell: Cell, color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, opacity: float = 1.0, line_width: float = 2.0, tube_radius: Optional[Union[float, str, ndarray]] = None, tube_radius_scale: Optional[float] = None, tube_radius_norm: Optional[Tuple[float, float]] = None, tube_radii: Optional[Tuple[float, float]] = None, root_marker: bool = False, mesh: bool = False, mesh_color: Optional[Union[str, ndarray, tuple]] = None, mesh_palette: Union[str, dict] = 'coolwarm', mesh_color_norm: Optional[Tuple[float, float]] = None, mesh_color_scale: Optional[Literal['log']] = None, mesh_opacity: float = 0.3, mesh_show_edges: bool = False, synapses: Literal['pre', 'post', 'both', True, False] = False, pre_anno: str = 'pre_syn', pre_color: Optional[Union[str, ndarray, tuple]] = None, pre_palette: Union[str, dict] = 'coolwarm', pre_color_norm: Optional[Tuple[float, float]] = None, post_anno: str = 'post_syn', post_color: Optional[Union[str, ndarray, tuple]] = None, post_palette: Union[str, dict] = 'coolwarm', post_color_norm: Optional[Tuple[float, float]] = None, syn_opacity: float = 1.0, syn_color_scale: Optional[Literal['log']] = None, syn_size: Optional[Union[str, ndarray, float]] = None, syn_size_norm: Optional[Tuple[float, float]] = None, syn_size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, syn_sizes: Optional[Tuple[float, float]] = None, plotter: Optional[Plotter] = None) -> Plotter

Render a :class:Cell — skeleton and optional annotations — in 3D.

Parameters:

  • cell (Cell) –

    Cell to render.

  • color (str, np.ndarray, or tuple, default: None ) –

    Skeleton color specification (see :func:plot_morphology_3d).

  • palette (str or dict, default: "coolwarm" ) –

    Colormap for skeleton color mapping. Any name from the matplotlib colormap registry <https://matplotlib.org/stable/gallery/color/colormap_reference.html>_ is accepted, including colormaps registered by third-party packages (e.g. cmocean, colorcet, cmcrameri) if they are installed. A dict maps discrete values to colors.

  • color_norm (tuple of float, default: None ) –

    (min, max) normalization range for skeleton color.

  • color_scale (log, default: "log" ) –

    Value transform applied before skeleton colormap projection (see :func:plot_morphology_3d).

  • opacity (float, default: 1.0 ) –

    Skeleton opacity.

  • line_width (float, default: 2.0 ) –

    Skeleton line width (used when tube_radius is None).

  • tube_radius (float, str, or np.ndarray, default: None ) –

    Skeleton tube radius (see :func:plot_morphology_3d).

  • tube_radius_scale (float, default: None ) –

    Multiplicative unit-conversion factor (e.g. 1/1000 for nm → µm).

  • tube_radius_norm (tuple of float, default: None ) –

    (min, max) input range / cap for per-vertex tube radii.

  • tube_radii (tuple of float, default: None ) –

    (min_radius, max_radius) output range for per-vertex tube radii.

  • root_marker (bool, default: False ) –

    If True, mark the root vertex with a sphere.

  • mesh (bool, default: False ) –

    If True and cell has a mesh layer, render the mesh surface.

  • mesh_color (str, np.ndarray, or tuple, default: None ) –

    Color specification for the mesh surface (see :func:plot_mesh_3d).

  • mesh_palette (str or dict, default: "coolwarm" ) –

    Colormap for mesh scalar color mapping (see palette).

  • mesh_color_norm (tuple of float, default: None ) –

    (min, max) clipping range for mesh color mapping.

  • mesh_color_scale (log, default: "log" ) –

    Value transform for mesh color mapping (see :func:plot_mesh_3d).

  • mesh_opacity (float, default: 0.3 ) –

    Opacity of the mesh surface. Defaults to semi-transparent so the skeleton remains visible underneath.

  • mesh_show_edges (bool, default: False ) –

    If True, draw wireframe edges on the mesh surface.

  • synapses ((pre, post, both), default: "pre" ) –

    Which synapse layers to render. False renders none; True or "both" renders both pre- and post-synaptic; "pre"/"post" renders only that side.

  • pre_anno (str, default: "pre_syn" ) –

    Name of the pre-synaptic annotation layer in cell.

  • pre_color (str, np.ndarray, or tuple, default: None ) –

    Color specification for the pre-synaptic layer.

  • pre_palette (str or dict, default: "coolwarm" ) –

    Colormap for pre-synaptic scalar color mapping (see palette).

  • pre_color_norm (tuple of float, default: None ) –

    (min, max) clipping range for pre-synaptic color mapping.

  • post_anno (str, default: "post_syn" ) –

    Name of the post-synaptic annotation layer in cell.

  • post_color (str, np.ndarray, or tuple, default: None ) –

    Color specification for the post-synaptic layer.

  • post_palette (str or dict, default: "coolwarm" ) –

    Colormap for post-synaptic scalar color mapping (see palette).

  • post_color_norm (tuple of float, default: None ) –

    (min, max) clipping range for post-synaptic color mapping.

  • syn_opacity (float, default: 1.0 ) –

    Opacity for all synapse spheres.

  • syn_color_scale (log, default: "log" ) –

    Value transform for synapse color mapping (see :func:plot_annotations_3d).

  • syn_size (str, np.ndarray, or float, default: None ) –

    Sphere radius for all synapse layers.

  • syn_size_norm (tuple of float, default: None ) –

    (min, max) clipping range for synapse size mapping.

  • syn_size_scale ((log, sqrt, cbrt), default: "log" ) –

    Value transform for synapse size mapping (see :func:plot_annotations_3d).

  • syn_sizes (tuple of float, default: None ) –

    (min_radius, max_radius) output range for synapse sphere radii, in the same world units as the cell vertices. When None (default), estimated from the synapse bounding box so spheres are visible regardless of coordinate scale (nm/µm/voxels).

  • plotter (Plotter, default: None ) –

    Existing plotter to add actors to.

Returns:

  • Plotter

    Plotter with the cell rendered.


3D Layer Plotting

plot_morphology_3d

plot_morphology_3d

plot_morphology_3d(cell: Union[Cell, SkeletonLayer], color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, opacity: Optional[Union[str, ndarray, float]] = 1.0, line_width: float = 2.0, tube_radius: Optional[Union[float, str, ndarray]] = None, tube_radius_scale: Optional[float] = None, tube_radius_norm: Optional[Tuple[float, float]] = None, tube_radii: Optional[Tuple[float, float]] = None, root_marker: bool = False, root_radius: Optional[float] = None, root_color: Optional[Union[str, tuple]] = None, plotter: Optional[Plotter] = None) -> Plotter

Plot a skeleton in 3D with flexible color and radius styling.

Parameters:

  • cell (Cell or SkeletonLayer) –

    Cell or skeleton to render.

  • color (str, np.ndarray, or tuple, default: None ) –

    Color specification. A string that matches a feature name is resolved to a per-vertex value array and mapped through palette. A string that does not match a feature is treated as a matplotlib color name. An array of shape (N,) is mapped through palette. An (N, 3) or (N, 4) array is used directly as RGB/RGBA.

  • palette (str or dict, default: "coolwarm" ) –

    Colormap for mapping scalar color values to RGB. Any name from the matplotlib colormap registry <https://matplotlib.org/stable/gallery/color/colormap_reference.html>_ is accepted, including colormaps registered by third-party packages (e.g. cmocean, colorcet, cmcrameri) if they are installed. A dict maps discrete values to colors.

  • color_norm (tuple of float, default: None ) –

    (min, max) normalization range for continuous color mapping, in the original (pre-transform) value space.

  • color_scale (log, default: "log" ) –

    Value transform applied before colormap projection. "log" log-transforms the feature values so the colormap is distributed linearly in log-space. color_norm bounds are still specified in the original value space.

  • opacity (str, np.ndarray, or float, default: 1.0 ) –

    Opacity of the skeleton. Feature name, per-vertex array, or scalar.

  • line_width (float, default: 2.0 ) –

    Line width in pixels. Used only when tube_radius is None.

  • tube_radius (float, str, or np.ndarray, default: None ) –

    Render as tubes with this radius. A scalar float gives a uniform tube; a string resolves to a feature name; an array specifies per-vertex radii directly.

  • tube_radius_scale (float, default: None ) –

    Multiplicative scale factor applied to all tube radius values after feature/array resolution. Useful for unit conversion — e.g. tube_radius_scale=1/1000 when the feature is in nm but the skeleton vertices are in µm. Applied before tube_radius_norm and tube_radii rescaling.

  • tube_radius_norm (tuple of float, default: None ) –

    (min, max) clip range for per-vertex tube radii. Values outside this range are clamped. When tube_radii is also given, the clipped values are further remapped to that output range; otherwise the original scale is preserved (clip only). Ignored when tube_radius is a scalar float.

  • tube_radii (tuple of float, default: None ) –

    (min_radius, max_radius) output range for per-vertex tube radii after normalization. Ignored when tube_radius is a scalar float.

  • root_marker (bool, default: False ) –

    If True, place a sphere at the root vertex.

  • root_radius (float, default: None ) –

    Radius for the root marker sphere. Falls back to tube_radius if it is a scalar float, then to 1.0.

  • root_color (str or tuple, default: None ) –

    Color for the root marker sphere. Defaults to the root vertex's mapped color when None.

  • plotter (Plotter, default: None ) –

    Existing plotter to add actors to.

Returns:

  • Plotter

    Plotter with the skeleton rendered.

plot_annotations_3d

plot_annotations_3d

plot_annotations_3d(annotation: PointCloudLayer, color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, opacity: float = 1.0, size: Optional[Union[str, ndarray, float]] = None, size_norm: Optional[Tuple[float, float]] = None, size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, sizes: Optional[Tuple[float, float]] = None, plotter: Optional[Plotter] = None) -> Plotter

Render a :class:PointCloudLayer annotation as 3D spheres.

Parameters:

  • annotation (PointCloudLayer) –

    Annotation layer to render.

  • color (str, np.ndarray, or tuple, default: None ) –

    Color specification. A string matching a feature name resolves to a per-point value array mapped through palette. Otherwise treated as a matplotlib color.

  • palette (str or dict, default: "coolwarm" ) –

    Colormap for scalar color mapping. Any name from the matplotlib colormap registry <https://matplotlib.org/stable/gallery/color/colormap_reference.html>_ is accepted, including colormaps registered by third-party packages (e.g. cmocean, colorcet, cmcrameri) if they are installed. A dict maps discrete values to colors.

  • color_norm (tuple of float, default: None ) –

    (min, max) clipping range for color mapping, in the original (pre-transform) value space.

  • color_scale (log, default: "log" ) –

    Value transform applied before colormap projection. "log" log-transforms the feature values so the colormap is distributed linearly in log-space. color_norm bounds are still specified in the original value space and are converted internally.

  • opacity (float, default: 1.0 ) –

    Overall opacity.

  • size (str, np.ndarray, or float, default: None ) –

    Sphere radius specification. A string resolves to a feature name. An array or float is used directly or rescaled to sizes.

  • size_norm (tuple of float, default: None ) –

    (min, max) clipping range for size mapping, in the original (pre-transform) value space.

  • size_scale ((log, sqrt, cbrt), default: "log" ) –

    Value transform applied before size normalization. "log" log-transforms values (linear spacing in log-space); "sqrt" takes the square root (useful when the feature is a cross-sectional area and radius ∝ √area); "cbrt" takes the cube root (useful when the feature is a volume and radius ∝ ∛volume). size_norm bounds are always specified in the original value space.

  • sizes (tuple of float, default: None ) –

    (min_radius, max_radius) output range for size rescaling, in the same world units as the annotation vertices. When None (default), estimated from the annotation's bounding box (approximately 0.1 %–0.8 % of the smallest spatial extent) so spheres are visible regardless of coordinate scale (nm/µm/voxels). Pass an explicit tuple to make markers bolder.

  • plotter (Plotter, default: None ) –

    Existing plotter to add actors to.

Returns:

  • Plotter

    Plotter with the annotation rendered.

plot_mesh_3d

plot_mesh_3d

plot_mesh_3d(mesh: MeshLayer, color: Optional[Union[str, ndarray, tuple]] = None, palette: Union[str, dict] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, color_scale: Optional[Literal['log']] = None, opacity: float = 1.0, show_edges: bool = False, plotter: Optional[Plotter] = None) -> Plotter

Render a :class:MeshLayer as a 3D surface using PyVista.

Parameters:

  • mesh (MeshLayer) –

    Mesh to render.

  • color (str, np.ndarray, or tuple, default: None ) –

    Color specification. May be:

    • A matplotlib color string (e.g. "steelblue") for a uniform surface color.
    • A tuple (r, g, b) or (r, g, b, a) for a uniform RGBA color.
    • A 1-D array of length n_vertices for per-vertex scalar coloring (mapped through palette).
    • A 2-D (n_vertices, 3) uint8 RGB array for direct per-vertex colors.
    • A feature name string present in the mesh to use those values for coloring.

    If None, PyVista's default surface color is used.

  • palette (str or dict, default: "coolwarm" ) –

    Colormap for scalar color mapping. Any name from the matplotlib colormap registry <https://matplotlib.org/stable/gallery/color/colormap_reference.html>_ is accepted, including colormaps registered by third-party packages (e.g. cmocean, colorcet, cmcrameri) if they are installed.

  • color_norm (tuple of float, default: None ) –

    (min, max) normalization range for scalar color mapping. Values outside this range are clamped.

  • color_scale (log, default: "log" ) –

    Transform applied to scalar values before color mapping. "log" applies a natural-log transform; color_norm bounds are interpreted in the original (pre-transform) space and converted internally.

  • opacity (float, default: 1.0 ) –

    Surface opacity. Values less than 1.0 make the mesh semi-transparent, useful when rendering alongside a skeleton.

  • show_edges (bool, default: False ) –

    If True, draw the mesh wireframe edges on top of the surface.

  • plotter (Plotter, default: None ) –

    Existing plotter to add the mesh to. If None, a new plotter is created.

Returns:

  • Plotter

    Plotter with the mesh surface rendered.

plot_graph_3d

plot_graph_3d

plot_graph_3d(graph: GraphLayer, node_color: Optional[Union[str, ndarray, tuple]] = None, node_palette: Union[str, dict] = 'coolwarm', node_color_norm: Optional[Tuple[float, float]] = None, node_color_scale: Optional[Literal['log']] = None, node_size: Optional[Union[str, ndarray, float]] = None, node_size_norm: Optional[Tuple[float, float]] = None, node_size_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, node_sizes: Optional[Tuple[float, float]] = None, node_opacity: float = 1.0, show_nodes: bool = True, edge_color: Optional[Union[str, ndarray, tuple]] = None, edge_palette: Union[str, dict] = 'coolwarm', edge_color_norm: Optional[Tuple[float, float]] = None, edge_color_scale: Optional[Literal['log']] = None, edge_radius: Optional[Union[str, float]] = None, edge_radius_norm: Optional[Tuple[float, float]] = None, edge_radius_scale: Optional[Literal['log', 'sqrt', 'cbrt']] = None, edge_radii: Optional[Tuple[float, float]] = None, edge_opacity: float = 1.0, line_width: float = 2.0, show_edges: bool = True, plotter: Optional[Plotter] = None) -> Plotter

Render a :class:GraphLayer as sphere glyphs at nodes with edges as lines or tubes.

All color and size features are per-vertex. When a feature name is given for an edge property, PyVista interpolates that value along each tube, creating a smooth gradient between the two endpoint values.

Parameters:

  • graph (GraphLayer) –

    Graph to render.

  • node_color (str, np.ndarray, or tuple, default: None ) –

    Color specification for node spheres. A feature name string resolves to per-vertex values mapped through node_palette. Otherwise treated as a matplotlib color.

  • node_palette (str or dict, default: "coolwarm" ) –

    Colormap for node scalar color mapping. Any name from the matplotlib colormap registry <https://matplotlib.org/stable/gallery/color/colormap_reference.html>_ is accepted, including colormaps registered by third-party packages (e.g. cmocean, colorcet, cmcrameri) if they are installed. A dict maps discrete values to colors.

  • node_color_norm (tuple of float, default: None ) –

    (min, max) clipping range for node color mapping, in the original (pre-transform) value space.

  • node_color_scale (log, default: "log" ) –

    Value transform before node colormap projection. "log" log-transforms values; node_color_norm stays in original space.

  • node_size (str, np.ndarray, or float, default: None ) –

    Sphere radius for each node. A feature name string resolves to per-vertex values rescaled to node_sizes.

  • node_size_norm (tuple of float, default: None ) –

    (min, max) clipping range for node size mapping, in the original (pre-transform) value space.

  • node_size_scale ((log, sqrt, cbrt), default: "log" ) –

    Value transform before node size normalization.

  • node_sizes (tuple of float, default: None ) –

    (min_radius, max_radius) output range for node sphere radii, in the same world units as the vertex coordinates. When None (default), a sensible range is estimated from the graph's bounding box (approximately 0.5 %–3 % of the smallest spatial extent).

  • node_opacity (float, default: 1.0 ) –

    Opacity for node spheres.

  • show_nodes (bool, default: True ) –

    If False, suppress node sphere rendering.

  • edge_color (str, np.ndarray, or tuple, default: None ) –

    Color specification for edges. A feature name string resolves to per-vertex values; each tube is then colored by smoothly interpolating between the two endpoint values. Otherwise treated as a uniform matplotlib color.

  • edge_palette (str or dict, default: "coolwarm" ) –

    Colormap for edge scalar color mapping (see node_palette).

  • edge_color_norm (tuple of float, default: None ) –

    (min, max) clipping range for edge color mapping.

  • edge_color_scale (log, default: "log" ) –

    Value transform before edge colormap projection.

  • edge_radius (str or float, default: None ) –

    Tube radius for edges. A scalar float gives a uniform tube radius. A feature name string resolves to per-vertex values rescaled to edge_radii; the tube radius interpolates between the two endpoint values along each edge. When None, edges are rendered as lines of line_width pixels.

  • edge_radius_norm (tuple of float, default: None ) –

    (min, max) clipping range for edge radius mapping.

  • edge_radius_scale ((log, sqrt, cbrt), default: "log" ) –

    Value transform before edge radius normalization.

  • edge_radii (tuple of float, default: None ) –

    (min_radius, max_radius) output range for per-vertex edge radii, in world units. When None (default), estimated from the graph's bounding box (approximately 0.1 %–1 % of the smallest extent).

  • edge_opacity (float, default: 1.0 ) –

    Opacity for edges.

  • line_width (float, default: 2.0 ) –

    Line width in pixels. Used only when edge_radius is None.

  • show_edges (bool, default: True ) –

    If False, suppress edge rendering.

  • plotter (Plotter, default: None ) –

    Existing plotter to add actors to.

Returns:

  • Plotter

    Plotter with the graph rendered.


3D Utilities

add_colorbar_3d

add_colorbar_3d

add_colorbar_3d(plotter: Plotter, palette: Union[str, Colormap] = 'coolwarm', color_norm: Optional[Tuple[float, float]] = None, label: Optional[str] = None, position_x: float = 0.85, position_y: float = 0.05, width: float = 0.1, height: float = 0.9, n_labels: int = 5, fmt: str = '%.3g', orientation: Literal['vertical', 'horizontal'] = 'vertical', n_colors: int = 256, **scalar_bar_kwargs) -> Plotter

Add a colorbar to a 3D plotter.

Because ossify's plot functions pre-map scalars to RGB before they reach VTK, PyVista has no mapper to build a scalar bar from. This helper creates a :class:vtkScalarBarActor directly from a :class:vtkLookupTable populated by the matplotlib colormap, then attaches it as a 2D viewport overlay. The actor has no spatial bounds, so the scene camera is unaffected.

Parameters:

  • plotter (Plotter) –

    Plotter to add the colorbar to.

  • palette (str or Colormap, default: "coolwarm" ) –

    Colormap name (any matplotlib-registered name) or Colormap object.

  • color_norm (tuple of float, default: None ) –

    (min, max) range for the colorbar. When None, the bar spans [0, 1].

  • label (str, default: None ) –

    Title text displayed alongside the colorbar.

  • position_x (float, default: 0.85 ) –

    Horizontal position of the colorbar (0 = left edge, 1 = right edge).

  • position_y (float, default: 0.05 ) –

    Vertical position of the colorbar bottom (0 = bottom, 1 = top).

  • width (float, default: 0.1 ) –

    Width of the colorbar as a fraction of the window.

  • height (float, default: 0.9 ) –

    Height of the colorbar as a fraction of the window.

  • n_labels (int, default: 5 ) –

    Number of tick labels along the bar.

  • fmt (str, default: "%.3g" ) –

    printf-style format string for tick labels.

  • orientation ((vertical, horizontal), default: "vertical" ) –

    Bar orientation.

  • n_colors (int, default: 256 ) –

    Number of colors sampled from palette into the lookup table.

  • **scalar_bar_kwargs

    Extra keyword arguments passed to :class:vtkScalarBarActor. Keys are converted from snake_case to SetPascalCase; unknown keys are silently ignored.

Returns:

  • Plotter

    The same plotter, with the colorbar added.

Examples:

>>> pl = plot_morphology_3d(cell, color="strahler_order", palette="viridis")
>>> add_colorbar_3d(pl, palette="viridis", color_norm=(1, 7), label="Strahler order")

orbit_3d

orbit_3d

orbit_3d(plotter: Plotter, output: Optional[str] = None, n_frames: int = 60, elevation: float = 0.0, factor: float = 2.0, framerate: int = 24, viewup: Optional[Tuple[float, float, float]] = None, window_size: Optional[Tuple[int, int]] = None, close: bool = True) -> Plotter

Orbit the camera around the scene, optionally saving to a file.

Generates a circular 360° orbital path around the viewup axis and either plays the orbit interactively or writes frames to a GIF / MP4 file.

Parameters:

  • plotter (Plotter) –

    Plotter with actors already added.

  • output (str, default: None ) –

    Path to write the animation to (.gif or .mp4). Requires imageio (already bundled with the [viz] extra). When None, the orbit is displayed interactively (or runs off-screen silently in headless contexts).

  • n_frames (int, default: 60 ) –

    Number of frames in the full 360° orbit. Higher values give a smoother animation; lower values give a smaller file. 60 frames @ 24 fps is a 2.5-second loop.

  • elevation (float, default: 0.0 ) –

    Camera elevation angle in degrees above the orbital plane, applied once before path generation. Positive values tilt the camera up; negative values tilt it down.

  • factor (float, default: 2.0 ) –

    Orbit radius factor relative to the scene bounding box. Larger values move the camera further from the scene center. Note that PyVista's :meth:Plotter.generate_orbital_path computes the radius from the scene's x and y extents only (ignoring z), so very-tall-in-z scenes may need a larger factor to avoid the camera path passing through the data.

  • framerate (int, default: 24 ) –

    Frames per second for file output. Ignored when output is None.

  • viewup (tuple of float, default: None ) –

    The axis around which the camera orbits and the camera's "up" direction during the orbit. [0, 0, 1] (z-up, PyVista's default) orbits in the xy plane around the z axis. [0, 1, 0] or [0, -1, 0] orbits around the y axis (common for cells where y is the depth/anatomical axis). When None, the plotter's current camera up vector is used.

  • window_size (tuple of int, default: None ) –

    (width, height) in pixels for rendered output frames. Larger sizes produce higher-resolution GIFs/MP4s at the cost of file size. When None, uses the plotter's existing window size (typically 1024 × 768 for newly-constructed plotters). For publication-quality output, try (1920, 1440) or larger.

  • close (bool, default: True ) –

    If True, close the plotter after the orbit completes (default for one-shot animation rendering). Pass close=False to keep the plotter alive — e.g. to add more actors and orbit again, or to capture screenshots after the orbit.

Returns:

  • Plotter

    The plotter passed in. By default the plotter is closed and not reusable; pass close=False to keep it alive.

Examples:

Interactive orbit:

>>> pl = plot_cell_3d(cell, color="red", tube_radius=500)
>>> orbit_3d(pl)

Save a high-res GIF orbiting around the depth axis:

>>> pl = plot_cell_3d(cell, color="red", tube_radius=500)
>>> orbit_3d(
...     pl,
...     output="neuron_orbit.gif",
...     n_frames=90,
...     viewup=(0, 1, 0),
...     window_size=(1920, 1080),
... )