Skip to content

Series

Overview

The Series class is the primary data structure in sleap-roots for working with SLEAP predictions. It provides a unified interface for loading prediction files, accessing root point data, and managing plant metadata.

Key features: - Load SLEAP .slp prediction files and .h5 image series - Access root points for primary, lateral, and crown roots - Quality control and filtering capabilities - Integration with pipeline classes for trait computation - Plotting and visualization

When to use: - You have SLEAP predictions and want to extract root point data - You need to load multiple plants for batch processing - You want to filter plants based on root count or quality - You're building custom trait computation workflows

Quick Example

import sleap_roots as sr

# Load a single plant
series = sr.Series.load(
    series_name="canola_plant1",
    h5_path="predictions.h5",
    primary_path="primary_roots.slp",
    lateral_path="lateral_roots.slp"
)

# Access root points
primary_pts = series.get_primary_points()  # (plants, frames, nodes, 2)
lateral_pts_list = series.get_lateral_points()  # List of arrays

print(f"Loaded {series.series_name}")
print(f"Primary shape: {primary_pts.shape}")
print(f"Lateral roots: {len(lateral_pts_list)}")

API Reference

Series Class

Series

Data and predictions for a single image series.

Attributes:

Name Type Description
series_name str

Unique identifier for the series.

h5_path Optional[str]

Optional path to the HDF5-formatted image series.

primary_path Optional[str]

Optional path to the primary root predictions file. At least one of the primary, lateral, or crown paths must be provided.

lateral_path Optional[str]

Optional path to the lateral root predictions file. At least one of the primary, lateral, or crown paths must be provided.

crown_path Optional[str]

Optional path to the crown predictions file. At least one of the primary, lateral, or crown paths must be provided.

primary_labels Optional[Labels]

Optional sio.Labels corresponding to the primary root predictions.

lateral_labels Optional[Labels]

Optional sio.Labels corresponding to the lateral root predictions.

crown_labels Optional[Labels]

Optional sio.Labels corresponding to the crown predictions.

video Optional[Video]

Optional sio.Video corresponding to the image series.

csv_path Optional[str]

Optional path to a metadata CSV. The CSV is keyed on a plant_qr_code column (matched against sample_uid). Any column in the CSV is readable via Series.get_metadata(column). The expected_count, group, qc_fail, and timepoint properties are thin wrappers that read specific well-known columns; users can add arbitrary additional columns and read them via get_metadata. See sleap_roots.metadata.build_metadata_csv for a helper that emits a CSV with the canonical column ordering.

sample_uid Optional[str]

Optional cross-scan stable identity for this series. Defaults to series_name when unset or empty. Coerced to str so CSV plant_qr_code lookups have predictable equality semantics.

Methods:

Name Description
load

Load a set of predictions for this series.

get_metadata

Generic CSV-column accessor keyed on sample_uid (and optional plant_id).

__len__

Length of the series (number of images).

__getitem__

Return labeled frames for predictions.

__iter__

Iterator for looping through predictions.

get_frame

Return labeled frames for predictions.

plot

Plot predictions on top of the image.

get_primary_points

Get primary root points.

get_lateral_points

Get lateral root points.

get_crown_points

Get crown root points.

Properties

expected_count: Fetch the expected plant count for this series from the CSV. group: Group name for the series from the CSV. qc_fail: Flag to indicate if the series failed QC from the CSV. timepoint: Numeric time-axis value for this series, coerced to float.


Loading Data

load classmethod

load(
    series_name: str,
    h5_path: Optional[str] = None,
    primary_path: Optional[str] = None,
    lateral_path: Optional[str] = None,
    crown_path: Optional[str] = None,
    csv_path: Optional[str] = None,
    sample_uid: Optional[str] = None,
) -> Series

Load a set of predictions for this series.

Parameters:

Name Type Description Default
series_name str

Unique identifier for the series.

required
h5_path Optional[str]

Optional path to the HDF5-formatted image series, which will be used to load the video.

None
primary_path Optional[str]

Optional path to the primary root '.slp' predictions file.

None
lateral_path Optional[str]

Optional path to the lateral root '.slp' predictions file.

None
crown_path Optional[str]

Optional path to the crown '.slp' predictions file.

None
csv_path Optional[str]

Optional path to a metadata CSV (keyed on plant_qr_code, matched against sample_uid). Read via get_metadata(column) or the wrapper properties (expected_count, group, qc_fail, timepoint).

None
sample_uid Optional[str]

Optional cross-scan stable identity. Defaults to series_name when unset or empty. Used as the CSV plant_qr_code lookup key by get_metadata and the expected_count/group/qc_fail properties.

None

Returns:

Type Description
Series

An instance of Series loaded with the specified predictions.

Source code in sleap_roots/series.py
@classmethod
def load(
    cls,
    series_name: str,
    h5_path: Optional[str] = None,
    primary_path: Optional[str] = None,
    lateral_path: Optional[str] = None,
    crown_path: Optional[str] = None,
    csv_path: Optional[str] = None,
    sample_uid: Optional[str] = None,
) -> "Series":
    """Load a set of predictions for this series.

    Args:
        series_name: Unique identifier for the series.
        h5_path: Optional path to the HDF5-formatted image series, which will be
            used to load the video.
        primary_path: Optional path to the primary root '.slp' predictions file.
        lateral_path: Optional path to the lateral root '.slp' predictions file.
        crown_path: Optional path to the crown '.slp' predictions file.
        csv_path: Optional path to a metadata CSV (keyed on `plant_qr_code`,
            matched against `sample_uid`). Read via `get_metadata(column)`
            or the wrapper properties (`expected_count`, `group`, `qc_fail`,
            `timepoint`).
        sample_uid: Optional cross-scan stable identity. Defaults to `series_name`
            when unset or empty. Used as the CSV `plant_qr_code` lookup key by
            `get_metadata` and the `expected_count`/`group`/`qc_fail` properties.

    Returns:
        An instance of Series loaded with the specified predictions.
    """
    # Initialize the labels as None
    primary_labels, lateral_labels, crown_labels = None, None, None

    # Attempt to load the predictions, with error handling
    try:
        if primary_path:
            # Make path object
            primary_path = Path(primary_path)
            # Check if the file exists
            if primary_path.exists():
                # Make the primary_path POSIX-compliant
                primary_path = primary_path.as_posix()
                # Load the primary predictions
                primary_labels = sio.load_slp(primary_path)
            else:
                print(f"Primary prediction file not found: {primary_path}")
        if lateral_path:
            # Make path object
            lateral_path = Path(lateral_path)
            # Check if the file exists
            if lateral_path.exists():
                # Make the lateral_path POSIX-compliant
                lateral_path = lateral_path.as_posix()
                # Load the lateral predictions
                lateral_labels = sio.load_slp(lateral_path)
            else:
                print(f"Lateral prediction file not found: {lateral_path}")
        if crown_path:
            # Make path object
            crown_path = Path(crown_path)
            # Check if the file exists
            if crown_path.exists():
                # Make the crown_path POSIX-compliant
                crown_path = crown_path.as_posix()
                # Load the crown predictions
                crown_labels = sio.load_slp(crown_path)
            else:
                print(f"Crown prediction file not found: {crown_path}")
    except Exception as e:
        print(f"Error loading prediction files: {e}")

    # Attempt to load the video, with error handling
    video = None
    try:
        if h5_path:
            # Make path object
            h5_path = Path(h5_path)
            # Check if the file exists
            if h5_path.exists():
                # Make the h5_path POSIX-compliant
                h5_path = h5_path.as_posix()
                # Load the video
                video = sio.Video.from_filename(h5_path)
                # Replace the filename in the labels with the h5_path
                for labels in [primary_labels, lateral_labels, crown_labels]:
                    if labels is not None:
                        labels.video.replace_filename(h5_path)
            else:
                print(f"Video file not found: {h5_path}")
    except Exception as e:
        print(f"Error loading video file {h5_path}: {e}")

    # Make the csv path POSIX-compliant
    if csv_path:
        csv_path = Path(csv_path).as_posix()

    return cls(
        series_name=series_name,
        h5_path=h5_path,
        primary_path=primary_path,
        lateral_path=lateral_path,
        crown_path=crown_path,
        primary_labels=primary_labels,
        lateral_labels=lateral_labels,
        crown_labels=crown_labels,
        video=video,
        csv_path=csv_path,
        sample_uid=sample_uid,
    )

Example:

import sleap_roots as sr

# Load plant with primary and lateral roots
series = sr.Series.load(
    series_name="my_plant",
    h5_path="data/predictions.h5",
    primary_path="data/primary.slp",
    lateral_path="data/lateral.slp"
)

# Load plant with all root types
series = sr.Series.load(
    series_name="complete_plant",
    h5_path="data/predictions.h5",
    primary_path="data/primary.slp",
    lateral_path="data/lateral.slp",
    crown_path="data/crown.slp"
)

# Load without video (points only)
series = sr.Series.load(
    series_name="points_only",
    primary_path="data/primary.slp",
    lateral_path="data/lateral.slp"
)

Common Use Cases:

# Dicot plant (primary + lateral)
series = sr.Series.load(
    "dicot1",
    h5_path="dicot.h5",
    primary_path="primary.slp",
    lateral_path="lateral.slp"
)

# Monocot plant (crown roots only)
series = sr.Series.load(
    "monocot1",
    h5_path="monocot.h5",
    crown_path="crown.slp"
)

# Multiple primary roots
series = sr.Series.load(
    "multiple_primary",
    h5_path="data.h5",
    primary_path="primary.slp"
)

See Also:


Accessing Root Points

get_primary_points

get_primary_points(frame_idx: int) -> ndarray

Get primary root points.

Parameters:

Name Type Description Default
frame_idx int

Frame index.

required

Returns:

Type Description
ndarray

Primary root points as array of shape (n_instances, n_nodes, 2).

Source code in sleap_roots/series.py
def get_primary_points(self, frame_idx: int) -> np.ndarray:
    """Get primary root points.

    Args:
        frame_idx: Frame index.

    Returns:
        Primary root points as array of shape `(n_instances, n_nodes, 2)`.
    """
    # Check that self.primary_labels is not None
    if self.primary_labels is None:
        raise ValueError("Primary labels are not available.")
    # Retrieve all available frames
    frames = self.get_frame(frame_idx)
    # Get the primary labeled frame
    primary_lf = frames.get("primary")
    # Get the ground truth instances and unused predictions
    gt_instances_pr = primary_lf.user_instances + primary_lf.unused_predictions
    # If there are no instances, return an empty array
    if len(gt_instances_pr) == 0:
        primary_pts = np.array([[(np.nan, np.nan), (np.nan, np.nan)]])
    # Otherwise, stack the instances into an array
    else:
        primary_pts = np.stack([inst.numpy() for inst in gt_instances_pr], axis=0)
    return primary_pts

Example:

import sleap_roots as sr

series = sr.Series.load(
    "plant1",
    h5_path="data.h5",
    primary_path="primary.slp"
)

# Get all primary root points
pts = series.get_primary_points()
print(pts.shape)  # (plants, frames, nodes, 2)

# Access specific frame
frame_pts = pts[0, 0]  # First plant, first frame
print(frame_pts.shape)  # (nodes, 2) - x,y coordinates

# Get x and y coordinates
x_coords = pts[..., 0]  # All x coordinates
y_coords = pts[..., 1]  # All y coordinates

See Also:


get_lateral_points

get_lateral_points(frame_idx: int) -> ndarray

Get lateral root points.

Parameters:

Name Type Description Default
frame_idx int

Frame index.

required

Returns:

Type Description
ndarray

Lateral root points as array of shape (n_instances, n_nodes, 2).

Source code in sleap_roots/series.py
def get_lateral_points(self, frame_idx: int) -> np.ndarray:
    """Get lateral root points.

    Args:
        frame_idx: Frame index.

    Returns:
        Lateral root points as array of shape `(n_instances, n_nodes, 2)`.
    """
    # Check that self.lateral_labels is not None
    if self.lateral_labels is None:
        raise ValueError("Lateral labels are not available.")
    # Retrieve all available frames
    frames = self.get_frame(frame_idx)
    # Get the lateral labeled frame
    lateral_lf = frames.get("lateral")
    # Get the ground truth instances and unused predictions
    gt_instances_lr = lateral_lf.user_instances + lateral_lf.unused_predictions
    # If there are no instances, return an empty array
    if len(gt_instances_lr) == 0:
        lateral_pts = np.array([[(np.nan, np.nan), (np.nan, np.nan)]])
    # Otherwise, stack the instances into an array
    else:
        lateral_pts = np.stack([inst.numpy() for inst in gt_instances_lr], axis=0)
    return lateral_pts

Example:

import sleap_roots as sr

series = sr.Series.load(
    "plant1",
    primary_path="primary.slp",
    lateral_path="lateral.slp"
)

# Get lateral root points (returns list)
lateral_pts_list = series.get_lateral_points()

# Each element is an array for one lateral root
for i, root_pts in enumerate(lateral_pts_list):
    print(f"Lateral root {i}: {root_pts.shape}")
    # Shape: (plants, frames, nodes, 2)

# Access specific lateral root
first_lateral = lateral_pts_list[0]
first_frame = first_lateral[0, 0]  # (nodes, 2)

Working with lateral roots:

# Count lateral roots
n_lateral = len(lateral_pts_list)

# Filter lateral roots with valid points
import numpy as np
valid_laterals = [
    pts for pts in lateral_pts_list
    if not np.all(np.isnan(pts))
]

# Get all lateral points as single array
from sleap_roots.points import join_pts
all_lateral = join_pts(lateral_pts_list)

See Also:


get_crown_points

get_crown_points(frame_idx: int) -> ndarray

Get crown root points.

Parameters:

Name Type Description Default
frame_idx int

Frame index.

required

Returns:

Type Description
ndarray

Crown root points as array of shape (n_instances, n_nodes, 2).

Source code in sleap_roots/series.py
def get_crown_points(self, frame_idx: int) -> np.ndarray:
    """Get crown root points.

    Args:
        frame_idx: Frame index.

    Returns:
        Crown root points as array of shape `(n_instances, n_nodes, 2)`.
    """
    # Check that self.crown_labels is not None
    if self.crown_labels is None:
        raise ValueError("Crown labels are not available.")
    # Retrieve all available frames
    frames = self.get_frame(frame_idx)
    # Get the crown labeled frame
    crown_lf = frames.get("crown")
    # Get the ground truth instances and unused predictions
    gt_instances_cr = crown_lf.user_instances + crown_lf.unused_predictions
    # If there are no instances, return an empty array
    if len(gt_instances_cr) == 0:
        crown_pts = np.array([[(np.nan, np.nan), (np.nan, np.nan)]])
    # Otherwise, stack the instances into an array
    else:
        crown_pts = np.stack([inst.numpy() for inst in gt_instances_cr], axis=0)
    return crown_pts

Example:

import sleap_roots as sr

series = sr.Series.load(
    "monocot1",
    h5_path="data.h5",
    crown_path="crown.slp"
)

# Get crown root points (returns list)
crown_pts_list = series.get_crown_points()

# Process each crown root
for i, root_pts in enumerate(crown_pts_list):
    print(f"Crown root {i}: {root_pts.shape}")
    # Shape: (plants, frames, nodes, 2)

See Also:


Quality Control

expected_count property

expected_count: Union[float, int]

Fetch the expected plant count for this series from the CSV.

Example:

import sleap_roots as sr

series = sr.Series.load(
    "plant1",
    primary_path="primary.slp",
    lateral_path="lateral.slp"
)

# Set expected root counts
series.expected_count(primary=1, lateral=5)

# Check if counts match
if not series.qc_fail():
    print("✓ Root counts match expected")
    # Proceed with analysis
else:
    print("✗ Unexpected root counts")
    # Handle QC failure

Batch filtering:

# Load multiple plants
plants = sr.load_series_from_slps(
    "data/",
    primary_pattern="*primary*.slp",
    lateral_pattern="*lateral*.slp"
)

# Set expected counts for all
for s in plants:
    s.expected_count(primary=1, lateral=5)

# Filter plants that pass QC
valid_plants = [s for s in plants if not s.qc_fail()]
print(f"Valid plants: {len(valid_plants)}/{len(plants)}")

See Also:


qc_fail property

qc_fail: Union[int, float]

Flag to indicate if the series failed QC from the CSV.

Example:

import sleap_roots as sr

series = sr.Series.load(
    "plant1",
    primary_path="primary.slp",
    lateral_path="lateral.slp"
)

# Set expected counts
series.expected_count(primary=1, lateral=5)

# Check QC status
if series.qc_fail():
    print(f"QC failed for {series.series_name}")
    print(f"Expected: 1 primary, 5 lateral")
    print(f"Found: {len(series.get_primary_points())}, {len(series.get_lateral_points())}")
else:
    print("QC passed - proceeding with analysis")

See Also:


Visualization

get_frame

get_frame(frame_idx: int) -> dict

Return labeled frames for primary, lateral, and crown predictions.

Parameters:

Name Type Description Default
frame_idx int

Integer frame number.

required

Returns:

Type Description
dict

Dictionary with keys 'primary', 'lateral', and 'crown', each corresponding

dict

to the sio.LabeledFrame from each set of predictions on the same frame. If

dict

any set of predictions is not available, its value will be None.

Source code in sleap_roots/series.py
def get_frame(self, frame_idx: int) -> dict:
    """Return labeled frames for primary, lateral, and crown predictions.

    Args:
        frame_idx: Integer frame number.

    Returns:
        Dictionary with keys 'primary', 'lateral', and 'crown', each corresponding
        to the `sio.LabeledFrame` from each set of predictions on the same frame. If
        any set of predictions is not available, its value will be None.
    """
    frames = {}

    # For primary predictions
    if self.primary_labels is not None:
        frames["primary"] = self.primary_labels.find(
            self.primary_labels.video, frame_idx, return_new=True
        )[0]
    else:
        frames["primary"] = None

    # For lateral predictions
    if self.lateral_labels is not None:
        frames["lateral"] = self.lateral_labels.find(
            self.lateral_labels.video, frame_idx, return_new=True
        )[0]
    else:
        frames["lateral"] = None

    # For crown predictions
    if self.crown_labels is not None:
        frames["crown"] = self.crown_labels.find(
            self.crown_labels.video, frame_idx, return_new=True
        )[0]
    else:
        frames["crown"] = None

    return frames

Example:

import sleap_roots as sr
import matplotlib.pyplot as plt

series = sr.Series.load(
    "plant1",
    h5_path="data.h5",
    primary_path="primary.slp"
)

# Get frame as image array
frame_img = series.get_frame(
    frame_idx=0,
    video_idx=0,
    return_rgb=True
)

# Display with matplotlib
plt.imshow(frame_img)
plt.title(f"{series.series_name} - Frame 0")
plt.axis('off')
plt.show()

See Also:

  • plot - High-level plotting with predictions overlaid

plot

plot(
    frame_idx: int, scale: float = 1.0, **kwargs
) -> Figure

Plot predictions on top of the image.

Parameters:

Name Type Description Default
frame_idx int

Frame index to visualize.

required
scale float

Relative size of the visualized image. Useful for plotting smaller images within notebooks.

1.0

Returns:

Type Description
Figure

matplotlib.figure.Figure object that shows predictions on top of images.

Source code in sleap_roots/series.py
def plot(
    self, frame_idx: int, scale: float = 1.0, **kwargs
) -> matplotlib.figure.Figure:
    """Plot predictions on top of the image.

    Args:
        frame_idx: Frame index to visualize.
        scale: Relative size of the visualized image. Useful for plotting smaller
            images within notebooks.

    Returns:
        matplotlib.figure.Figure object that shows predictions on top of images.
    """
    # Check if the video is available
    if self.video is None:
        raise ValueError("Video is not available. Specify the h5_path to load it.")

    # Retrieve all available frames
    frames = self.get_frame(frame_idx)

    # Generate the color palette from seaborn
    cmap = sns.color_palette("tab10")

    # Define the order of preference for the predictions for plotting the image
    prediction_order = ["primary", "lateral", "crown"]

    # Variable to keep track if the image has been plotted
    image_plotted = False

    # First, find the first available prediction to plot the image
    for prediction in prediction_order:
        labeled_frame = frames.get(prediction)
        if labeled_frame is not None and not image_plotted:
            # Plot the image
            plot_img(labeled_frame.image, scale=scale)
            # Set the flag to True to avoid plotting the image again
            image_plotted = True

    # Then, iterate through all predictions to plot instances
    for i, prediction in enumerate(prediction_order):
        labeled_frame = frames.get(prediction)
        if labeled_frame is not None:
            # Use the color map index for each prediction type
            # Modulo the length of the color map to avoid index out of range
            color = cmap[i % len(cmap)]

            # Plot the instances
            plot_instances(labeled_frame.instances, cmap=[color], **kwargs)

    # Capture the current plot after calling plot_img and plot_instances.
    fig = plt.gcf()

    # Close the captured fig to avoid duplicate rendering from Jupyter.
    plt.close(fig)

    # Return the figure. In a cell, Jupyter will automatically render the plot.
    return fig

Example:

import sleap_roots as sr
import matplotlib.pyplot as plt

series = sr.Series.load(
    "plant1",
    h5_path="data.h5",
    primary_path="primary.slp",
    lateral_path="lateral.slp"
)

# Plot with predictions overlaid
fig = series.plot(
    frame_idx=0,
    primary=True,
    lateral=True,
    plot_scale=1.0
)
plt.show()

# Customize plot
fig = series.plot(
    frame_idx=5,
    primary=True,
    lateral=True,
    plot_scale=0.5  # Smaller display
)
plt.title(f"{series.series_name} - Frame 5")
plt.show()

See Also:


Properties

The Series class has the following attributes:

  • series_name: str - Name identifier for the plant
  • h5_path: str | None - Path to H5 image file
  • primary_path: str | None - Path to primary root SLP file
  • lateral_path: str | None - Path to lateral root SLP file
  • crown_path: str | None - Path to crown root SLP file
  • primary_labels: sleap_io.Labels | None - Loaded primary root labels
  • lateral_labels: sleap_io.Labels | None - Loaded lateral root labels
  • crown_labels: sleap_io.Labels | None - Loaded crown root labels
  • video: sleap_io.Video | None - Loaded video object
  • csv_path: str | None - Path for CSV export

Example:

import sleap_roots as sr

series = sr.Series.load(
    "plant1",
    h5_path="data.h5",
    primary_path="primary.slp"
)

# Access properties
print(f"Plant name: {series.series_name}")
print(f"H5 path: {series.h5_path}")
print(f"Primary predictions: {series.primary_path}")
print(f"Has video: {series.video is not None}")

# Check what was loaded
has_primary = series.primary_labels is not None
has_lateral = series.lateral_labels is not None
has_crown = series.crown_labels is not None
print(f"Loaded: primary={has_primary}, lateral={has_lateral}, crown={has_crown}")


Utility Functions

These module-level functions help with batch loading:

find_all_h5_paths

find_all_h5_paths(
    data_folders: Union[str, List[str]],
) -> List[str]

Find all .h5 paths from a list of folders.

Parameters:

Name Type Description Default
data_folders Union[str, List[str]]

Path or list of paths to folders containing .h5 paths.

required

Returns:

Type Description
List[str]

A list of filenames to .h5 paths.

Source code in sleap_roots/series.py
def find_all_h5_paths(data_folders: Union[str, List[str]]) -> List[str]:
    """Find all .h5 paths from a list of folders.

    Args:
        data_folders: Path or list of paths to folders containing .h5 paths.

    Returns:
        A list of filenames to .h5 paths.
    """
    if type(data_folders) != list:
        data_folders = [data_folders]

    h5_paths = []
    for data_folder in data_folders:
        h5_paths.extend([Path(p).as_posix() for p in Path(data_folder).glob("*.h5")])
    return h5_paths

Example:

from sleap_roots import find_all_h5_paths

# Find all H5 files in directory
h5_files = find_all_h5_paths("data/predictions/")
print(f"Found {len(h5_files)} H5 files")

for h5_path in h5_files:
    print(h5_path)


find_all_slp_paths

find_all_slp_paths(
    data_folders: Union[str, List[str]],
) -> List[str]

Find all .slp paths from a list of folders.

Parameters:

Name Type Description Default
data_folders Union[str, List[str]]

Path or list of paths to folders containing .slp paths.

required

Returns:

Type Description
List[str]

A list of filenames to .slp paths.

Source code in sleap_roots/series.py
def find_all_slp_paths(data_folders: Union[str, List[str]]) -> List[str]:
    """Find all .slp paths from a list of folders.

    Args:
        data_folders: Path or list of paths to folders containing .slp paths.

    Returns:
        A list of filenames to .slp paths.
    """
    if type(data_folders) != list:
        data_folders = [data_folders]

    slp_paths = []
    for data_folder in data_folders:
        slp_paths.extend([Path(p).as_posix() for p in Path(data_folder).glob("*.slp")])
    return slp_paths

Example:

from sleap_roots import find_all_slp_paths

# Find all SLP files in directory
slp_files = find_all_slp_paths("data/predictions/")
print(f"Found {len(slp_files)} SLP files")

# Filter by pattern
primary_files = [f for f in slp_files if 'primary' in f.name]
lateral_files = [f for f in slp_files if 'lateral' in f.name]


load_series_from_h5s

load_series_from_h5s(
    h5_paths: List[str],
    model_id: str,
    csv_path: Optional[str] = None,
) -> List[Series]

Load a list of Series from a list of .h5 paths.

To load the Series, the files must be named with the following convention: h5_path: '/path/to/scan/series_name.h5' primary_path: '/path/to/scan/series_name.model{model_id}.rootprimary.slp' lateral_path: '/path/to/scan/series_name.model{model_id}.rootlateral.slp' crown_path: '/path/to/scan/series_name.model{model_id}.rootcrown.slp'

Our pipeline outputs prediction files with this format: //scan{scan_id}.model{model_id}.root{model_type}.slp

Parameters:

Name Type Description Default
h5_paths List[str]

List of paths to .h5 files.

required
csv_path Optional[str]

Optional path to the CSV file containing the expected plant count.

None

Returns:

Type Description
List[Series]

A list of Series loaded with the specified .h5 files.

Source code in sleap_roots/series.py
def load_series_from_h5s(
    h5_paths: List[str], model_id: str, csv_path: Optional[str] = None
) -> List[Series]:
    """Load a list of Series from a list of .h5 paths.

    To load the `Series`, the files must be named with the following convention:
    h5_path: '/path/to/scan/series_name.h5'
    primary_path: '/path/to/scan/series_name.model{model_id}.rootprimary.slp'
    lateral_path: '/path/to/scan/series_name.model{model_id}.rootlateral.slp'
    crown_path: '/path/to/scan/series_name.model{model_id}.rootcrown.slp'

    Our pipeline outputs prediction files with this format:
    /<output_folder>/scan{scan_id}.model{model_id}.root{model_type}.slp

    Args:
        h5_paths: List of paths to .h5 files.
        csv_path: Optional path to the CSV file containing the expected plant count.

    Returns:
        A list of Series loaded with the specified .h5 files.
    """
    series_list = []
    for h5_path in h5_paths:
        # Extract the series name from the h5 path
        series_name = Path(h5_path).name.split(".")[0]
        # Generate the paths for the primary, lateral, and crown predictions
        primary_path = h5_path.replace(".h5", f".model{model_id}.rootprimary.slp")
        lateral_path = h5_path.replace(".h5", f".model{model_id}.rootlateral.slp")
        crown_path = h5_path.replace(".h5", f".model{model_id}.rootcrown.slp")
        # Load the Series
        series = Series.load(
            series_name,
            h5_path=h5_path,
            primary_path=primary_path,
            lateral_path=lateral_path,
            crown_path=crown_path,
            csv_path=csv_path,
        )
        series_list.append(series)
    return series_list

Example:

from sleap_roots import load_series_from_h5s

# Load all plants from H5 directory
plants = load_series_from_h5s(
    h5_dir="data/h5_files/",
    primary_pattern="*primary*.slp",
    lateral_pattern="*lateral*.slp"
)

print(f"Loaded {len(plants)} plants")

# Process each plant
for series in plants:
    primary_pts = series.get_primary_points()
    print(f"{series.series_name}: {primary_pts.shape}")


load_series_from_slps

load_series_from_slps(
    slp_paths: List[str],
    h5s: bool = False,
    csv_path: Optional[str] = None,
) -> List[Series]

Load a list of Series from a list of .slp paths.

To load the Series, the files must be named with the following convention. The slp_paths are expeted to have the series_name in the filename and "primary", "lateral", or "crown" in the filename to differentiate the predictions. h5_path: '/path/to/scan/series_name.h5' Note that everything is expected to be in the same folder.

Our pipeline outputs prediction files with this format: //scan{scan_id}.model{model_id}.root{model_type}.slp

Parameters:

Name Type Description Default
slp_paths List[str]

List of paths to .slp files.

required
h5s bool

Boolean flag to indicate if the .h5 files are available. Default is False.

False
csv_path Optional[str]

Optional path to the CSV file containing the expected plant count.

None
Source code in sleap_roots/series.py
def load_series_from_slps(
    slp_paths: List[str], h5s: bool = False, csv_path: Optional[str] = None
) -> List[Series]:
    """Load a list of Series from a list of .slp paths.

    To load the `Series`, the files must be named with the following convention.
    The `slp_paths` are expeted to have the `series_name` in the filename and "primary",
    "lateral", or "crown" in the filename to differentiate the predictions.
    h5_path: '/path/to/scan/series_name.h5'
    Note that everything is expected to be in the same folder.

    Our pipeline outputs prediction files with this format:
    /<output_folder>/scan{scan_id}.model{model_id}.root{model_type}.slp


    Args:
        slp_paths: List of paths to .slp files.
        h5s: Boolean flag to indicate if the .h5 files are available. Default is False.
        csv_path: Optional path to the CSV file containing the expected plant count.
    """
    series_list = []
    series_names = list(set([Path(p).name.split(".")[0] for p in slp_paths]))
    for series_name in series_names:
        # Generate the paths for the primary, lateral, and crown predictions
        primary_path = [p for p in slp_paths if series_name in p and "primary" in p]
        lateral_path = [p for p in slp_paths if series_name in p and "lateral" in p]
        crown_path = [p for p in slp_paths if series_name in p and "crown" in p]
        # Check if the .h5 files are available
        if h5s:
            # Get directory of the h5s
            h5_dir = Path(slp_paths[0]).parent
            # Create path to the .h5 file
            h5_path = h5_dir / f"{series_name}.h5"
        else:
            h5_path = None
        # Load the Series
        series = Series.load(
            series_name,
            primary_path=primary_path[0] if primary_path else None,
            lateral_path=lateral_path[0] if lateral_path else None,
            crown_path=crown_path[0] if crown_path else None,
            h5_path=h5_path,
            csv_path=csv_path,
        )
        series_list.append(series)
    return series_list

Example:

from sleap_roots import load_series_from_slps

# Load all plants from SLP directory
plants = load_series_from_slps(
    slp_dir="data/predictions/",
    primary_pattern="*primary*.slp",
    lateral_pattern="*lateral*.slp",
    crown_pattern="*crown*.slp"
)

print(f"Loaded {len(plants)} plants")

# Batch processing
for series in plants:
    # Set QC expectations
    series.expected_count(primary=1, lateral=5)

    # Filter by QC
    if not series.qc_fail():
        # Run analysis
        primary_pts = series.get_primary_points()
        lateral_pts_list = series.get_lateral_points()


Complete Workflow Example

import sleap_roots as sr
import numpy as np

# 1. Load plant data
series = sr.Series.load(
    series_name="arabidopsis_1",
    h5_path="data/arabidopsis.h5",
    primary_path="data/arabidopsis_primary.slp",
    lateral_path="data/arabidopsis_lateral.slp"
)

# 2. Quality control
series.expected_count(primary=1, lateral=8)
if series.qc_fail():
    print(f"Warning: {series.series_name} failed QC")

# 3. Extract root points
primary_pts = series.get_primary_points()
lateral_pts_list = series.get_lateral_points()

print(f"Primary roots: {primary_pts.shape}")
print(f"Lateral roots: {len(lateral_pts_list)}")

# 4. Compute custom trait
from sleap_roots import get_root_lengths

primary_lengths = get_root_lengths(primary_pts)
lateral_lengths = [get_root_lengths(pts) for pts in lateral_pts_list]

print(f"Primary root length: {np.nanmean(primary_lengths):.2f} pixels")
print(f"Mean lateral length: {np.nanmean([np.nanmean(l) for l in lateral_lengths]):.2f} pixels")

# 5. Visualize
import matplotlib.pyplot as plt
fig = series.plot(frame_idx=0, primary=True, lateral=True)
plt.show()

# 6. Or use a pipeline for full trait extraction
pipeline = sr.DicotPipeline()
traits = pipeline.fit_series(series)
print(f"Computed {len(traits)} traits")

Advanced Usage

Working with Multiple Frames

import sleap_roots as sr

series = sr.Series.load("plant1", primary_path="primary.slp")
primary_pts = series.get_primary_points()

# Shape: (plants, frames, nodes, 2)
n_plants, n_frames, n_nodes, _ = primary_pts.shape

# Analyze each frame
for frame_idx in range(n_frames):
    frame_pts = primary_pts[0, frame_idx]  # (nodes, 2)

    # Compute per-frame traits
    from sleap_roots import get_root_angle
    angle = get_root_angle(frame_pts[np.newaxis, :, :])
    print(f"Frame {frame_idx}: angle = {angle[0]:.1f}°")

Batch Processing

import sleap_roots as sr
from pathlib import Path

# Load all plants
plants = sr.load_series_from_slps(
    slp_dir="data/experiment1/",
    primary_pattern="*primary*.slp",
    lateral_pattern="*lateral*.slp"
)

# Process with QC
results = []
for series in plants:
    series.expected_count(primary=1, lateral=5)

    if not series.qc_fail():
        # Run pipeline
        pipeline = sr.DicotPipeline()
        traits = pipeline.fit_series(series)
        results.append({
            'plant': series.series_name,
            **traits
        })

# Export results
import pandas as pd
df = pd.DataFrame(results)
df.to_csv("experiment1_results.csv", index=False)
print(f"Processed {len(results)} valid plants")


See Also