Common Workflows¶
Complete end-to-end examples for typical sleap-roots analysis tasks.
Workflow 1: Quick Pipeline Analysis¶
The fastest way to extract traits from SLEAP predictions.
import sleap_roots as sr
# Load data
series = sr.Series.load(
series_name="rice_plant1",
h5_path="predictions.h5",
primary_path="primary_roots.slp",
lateral_path="lateral_roots.slp"
)
# Choose appropriate pipeline
pipeline = sr.YoungerMonocotPipeline()
# Extract all traits
traits_dict = pipeline.compute_plant_traits(series)
# Access specific traits
print(f"Primary root count: {traits_dict['primary_count']}")
print(f"Primary max length: {traits_dict['primary_max_length']:.2f} px")
print(f"Lateral count: {traits_dict['lateral_count']}")
print(f"Network length: {traits_dict['network_length_lower']:.2f} px")
When to use: You need standard traits quickly and don't need custom computations.
See also: Pipelines documentation
Workflow 2: Custom Trait Computation¶
Compute specific traits with fine-grained control.
import sleap_roots as sr
import numpy as np
# Load data
series = sr.Series.load("canola_plant1", primary_path="primary.slp")
# Get point arrays
primary_pts = series.get_primary_points() # (plants, frames, nodes, 2)
# Compute multiple traits
lengths = sr.get_root_lengths(primary_pts)
angles = sr.get_root_angle(primary_pts, n_points=5)
curve_indices = sr.get_curve_index(primary_pts)
# Get first frame, first plant
frame_0 = 0
plant_0 = 0
print(f"Primary root length: {lengths[plant_0, frame_0]:.2f} px")
print(f"Growth angle: {angles[plant_0, frame_0]:.2f}°")
print(f"Curvature index: {curve_indices[plant_0, frame_0]:.3f}")
# Compute summary statistics across time
mean_length = np.nanmean(lengths[plant_0, :])
std_angle = np.nanstd(angles[plant_0, :])
print(f"\nAcross {series.frame_count} frames:")
print(f"Mean length: {mean_length:.2f} px")
print(f"Angle variability: {std_angle:.2f}°")
When to use: You need specific traits not available in pipelines, or want custom processing.
Workflow 3: Lateral Root Analysis¶
Analyze lateral root emergence and distribution patterns.
import sleap_roots as sr
import numpy as np
# Load with laterals
series = sr.Series.load(
"wheat_plant1",
primary_path="primary.slp",
lateral_path="lateral.slp"
)
# Get lateral points (list of arrays)
lateral_pts_list = series.get_lateral_points()
primary_pts = series.get_primary_points()
# Analyze each lateral root
lateral_count = len(lateral_pts_list)
print(f"Detected {lateral_count} lateral roots\n")
for i, lateral_pts in enumerate(lateral_pts_list):
# Compute traits for this lateral
length = sr.get_root_lengths(lateral_pts)[0, 0]
angle = sr.get_root_angle(lateral_pts)[0, 0]
print(f"Lateral {i+1}:")
print(f" Length: {length:.2f} px")
print(f" Angle: {angle:.2f}°")
# Get lateral base positions along primary
bases = sr.get_bases(primary_pts, lateral_pts_list)
base_ys = bases[:, 1] # y-coordinates
# Compute emergence density
primary_length = sr.get_root_lengths(primary_pts)[0, 0]
base_density = sr.get_base_ct_density(primary_pts, lateral_pts_list)[0, 0]
print(f"\nEmergence pattern:")
print(f"Primary length: {primary_length:.2f} px")
print(f"Lateral density: {base_density:.3f} laterals/px")
print(f"Base positions (y): {base_ys}")
When to use: Studying lateral root development, emergence patterns, or architecture.
See also: Bases, Lateral Pipeline
Workflow 4: Temporal Growth Analysis¶
Track root growth over time using multiple frames.
import sleap_roots as sr
import numpy as np
import matplotlib.pyplot as plt
# Load time series data
series = sr.Series.load("timeseries_plant", primary_path="primary.slp")
primary_pts = series.get_primary_points() # (1, frames, nodes, 2)
frames = series.frame_count
plant_idx = 0
# Compute traits over time
lengths_over_time = []
angles_over_time = []
tip_ys_over_time = []
for frame_idx in range(frames):
frame_pts = primary_pts[plant_idx:plant_idx+1, frame_idx:frame_idx+1]
length = sr.get_root_lengths(frame_pts)[0, 0]
angle = sr.get_root_angle(frame_pts)[0, 0]
tip_y = sr.get_tip_ys(frame_pts)[0, 0]
lengths_over_time.append(length)
angles_over_time.append(angle)
tip_ys_over_time.append(tip_y)
# Analyze growth rate
growth_rates = np.diff(lengths_over_time)
mean_growth_rate = np.mean(growth_rates)
print(f"Analysis across {frames} frames:")
print(f"Initial length: {lengths_over_time[0]:.2f} px")
print(f"Final length: {lengths_over_time[-1]:.2f} px")
print(f"Total growth: {lengths_over_time[-1] - lengths_over_time[0]:.2f} px")
print(f"Mean growth rate: {mean_growth_rate:.2f} px/frame")
# Plot growth trajectory
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].plot(lengths_over_time)
axes[0].set_title('Root Length Over Time')
axes[0].set_xlabel('Frame')
axes[0].set_ylabel('Length (px)')
axes[1].plot(angles_over_time)
axes[1].set_title('Growth Angle Over Time')
axes[1].set_xlabel('Frame')
axes[1].set_ylabel('Angle (°)')
axes[2].plot(tip_ys_over_time)
axes[2].set_title('Tip Position Over Time')
axes[2].set_xlabel('Frame')
axes[2].set_ylabel('Y Position (px)')
plt.tight_layout()
plt.savefig('growth_analysis.png', dpi=150)
print("\nSaved growth_analysis.png")
When to use: Time-lapse experiments, growth rate analysis, gravitropism studies.
See also: Series documentation, Tips
Workflow 5: Network-Level Spatial Analysis¶
Analyze whole root system architecture and spatial distribution.
import sleap_roots as sr
from sleap_roots import join_pts
# Load complete root system
series = sr.Series.load(
"mature_plant",
primary_path="primary.slp",
lateral_path="lateral.slp"
)
# Get all roots
primary_pts = series.get_primary_points()
lateral_pts_list = series.get_lateral_points()
# Combine into single point array
all_pts = join_pts([primary_pts] + lateral_pts_list)
# Network-level metrics
network_length = sr.get_network_length(all_pts)
wd_ratio = sr.get_network_width_depth_ratio(all_pts)
bbox = sr.get_bbox(all_pts)
print("Network Metrics:")
print(f"Total network length: {network_length:.2f} px")
print(f"Width:Depth ratio: {wd_ratio:.2f}")
print(f"Bounding box: {bbox}")
# Spatial coverage
hull_area = sr.convhull.get_chull_area(all_pts)
hull_perimeter = sr.convhull.get_chull_perimeter(all_pts)
print(f"\nSpatial Coverage:")
print(f"Convex hull area: {hull_area:.2f} px²")
print(f"Convex hull perimeter: {hull_perimeter:.2f} px")
# Ellipse fit for compact representation
pts_2d = sr.points.get_all_pts_array(all_pts) # Flatten to (n_points, 2)
ellipse = sr.ellipse.fit_ellipse(pts_2d)
a = sr.ellipse.get_ellipse_a(ellipse)
b = sr.ellipse.get_ellipse_b(ellipse)
print(f"\nEllipse major axis: {a:.2f} px, minor axis: {b:.2f} px")
# Distribution analysis using scanlines
y_positions = [100, 200, 300, 400, 500]
for y in y_positions:
intersections = sr.count_scanline_intersections(all_pts, y=y)
print(f"Intersections at y={y}: {intersections}")
When to use: Characterizing root system architecture, comparing genotypes, spatial phenotyping.
See also: Network Length, Convex Hull, Scanline
Workflow 6: Batch Processing Multiple Plants¶
Process multiple plant series and aggregate results.
import sleap_roots as sr
import pandas as pd
from pathlib import Path
# Define plant data
plants = [
{"name": "plant1", "h5": "plant1.h5", "primary": "plant1_primary.slp"},
{"name": "plant2", "h5": "plant2.h5", "primary": "plant2_primary.slp"},
{"name": "plant3", "h5": "plant3.h5", "primary": "plant3_primary.slp"},
]
# Load all series
all_series = []
for plant_info in plants:
series = sr.Series.load(
plant_info["name"],
h5_path=plant_info["h5"],
primary_path=plant_info["primary"]
)
all_series.append(series)
# Run batch pipeline processing
pipeline = sr.DicotPipeline()
batch_traits = pipeline.compute_batch_traits(all_series)
# Convert to DataFrame
df = pd.DataFrame(batch_traits)
df["plant_name"] = [p["name"] for p in plants]
print("Results:")
print(df[["plant_name", "primary_length", "primary_angle", "lateral_count"]])
# Save to CSV
df.to_csv("batch_analysis_results.csv", index=False)
print("\nSaved batch_analysis_results.csv")
# Compute summary statistics
print("\nSummary Statistics:")
for col in ["primary_length", "primary_angle", "lateral_count"]:
stats = sr.get_summary(df[col].values, prefix=f"{col}_")
print(f"\n{col}:")
print(f" Mean: {stats[f'{col}_mean']:.2f}")
print(f" Std: {stats[f'{col}_std']:.2f}")
print(f" Range: {stats[f'{col}_min']:.2f} - {stats[f'{col}_max']:.2f}")
When to use: High-throughput phenotyping, genotype comparisons, experimental studies.
See also: Pipelines, Summary Statistics
Workflow 7: Quality Control and Filtering¶
Filter out invalid roots and handle edge cases.
import sleap_roots as sr
import numpy as np
# Load data
series = sr.Series.load(
"experimental_data",
primary_path="primary.slp",
lateral_path="lateral.slp"
)
lateral_pts_list = series.get_lateral_points()
print(f"Initial lateral count: {len(lateral_pts_list)}")
# Filter out roots with NaN values (incomplete tracking)
valid_laterals = sr.filter_roots_with_nans(lateral_pts_list)
print(f"Valid laterals after NaN filtering: {len(valid_laterals)}")
# Additional quality filtering: remove very short roots
min_length_threshold = 10.0 # pixels
filtered_laterals = []
for lateral_pts in valid_laterals:
length = sr.get_root_lengths(lateral_pts)[0, 0]
if length >= min_length_threshold:
filtered_laterals.append(lateral_pts)
print(f"Valid laterals after length filtering: {len(filtered_laterals)}")
# Compute traits only on valid roots
if len(filtered_laterals) > 0:
lengths = [sr.get_root_lengths(pts)[0, 0] for pts in filtered_laterals]
angles = [sr.get_root_angle(pts)[0, 0] for pts in filtered_laterals]
print(f"\nFiltered Lateral Statistics:")
print(f"Mean length: {np.mean(lengths):.2f} px")
print(f"Mean angle: {np.mean(angles):.2f}°")
print(f"Length range: {np.min(lengths):.2f} - {np.max(lengths):.2f} px")
else:
print("No valid lateral roots after filtering")
# Check primary root quality
primary_pts = series.get_primary_points()
has_nans = np.any(np.isnan(primary_pts))
if has_nans:
print("\nWarning: Primary root contains NaN values")
# Handle by using only valid frames
valid_frames = ~np.any(np.isnan(primary_pts), axis=(0, 2, 3))
clean_pts = primary_pts[:, valid_frames, :, :]
print(f"Using {np.sum(valid_frames)}/{len(valid_frames)} valid frames")
else:
print("\nPrimary root tracking is complete")
When to use: Working with incomplete tracking data, ensuring data quality, debugging.
See also: Points utilities, Series
Workflow 8: Multiple Dicot Plants¶
Process multiple dicot plants simultaneously with specialized pipeline.
import sleap_roots as sr
# Load series with multiple plants
series = sr.Series.load(
"arabidopsis_tray",
h5_path="predictions.h5",
primary_path="primary_roots.slp",
lateral_path="lateral_roots.slp"
)
# Use multiple dicot pipeline
pipeline = sr.MultipleDicotPipeline()
traits_dict = pipeline.compute_multiple_dicots_traits(series)
# Access per-plant traits
print(f"Plants detected: {len(traits_dict['primary_length'])}")
for i, (length, angle, lat_count) in enumerate(zip(
traits_dict['primary_length'],
traits_dict['primary_angle'],
traits_dict['lateral_count']
)):
print(f"\nPlant {i+1}:")
print(f" Primary length: {length:.2f} px")
print(f" Primary angle: {angle:.2f}°")
print(f" Lateral count: {lat_count}")
When to use: High-throughput imaging with multiple plants per image.
See also: MultipleDicotPipeline
Next Steps¶
- API Reference - Explore all available functions
- Pipelines Guide - Choose the right pipeline
- Tutorials - Interactive learning