Skip to content

analyzer

sleap_nn.config_generator.analyzer

Dataset analysis utilities for config generation.

This module provides tools for extracting statistics from SLP files to inform automatic configuration of training parameters.

Classes:

Name Description
DatasetStats

Statistics extracted from an SLP file for auto-configuration.

ViewType

Camera view orientation for augmentation defaults.

Functions:

Name Description
analyze_slp

Analyze an SLP file and extract statistics for auto-configuration.

DatasetStats dataclass

Statistics extracted from an SLP file for auto-configuration.

Attributes:

Name Type Description
slp_path str

Path to the source SLP file.

num_labeled_frames int

Number of frames with user labels.

num_videos int

Number of video sources.

max_height int

Maximum image height across videos.

max_width int

Maximum image width across videos.

num_channels int

Number of image channels (1=grayscale, 3=RGB).

max_instances_per_frame int

Maximum instances in any single frame.

avg_instances_per_frame float

Average instances per frame.

max_bbox_size float

Maximum bounding box dimension of any instance (max(width, height) per instance).

avg_bbox_size float

Average bounding box size, defined as mean(max(width, height)) across instances.

avg_bbox_diagonal float

Average bounding box diagonal (mean(sqrt(width**2 + height**2))). Matches the web app's slpData.avgAnimalSize and is used for default max_stride bucket recommendations.

num_nodes int

Number of skeleton nodes.

num_edges int

Number of skeleton edges.

node_names List[str]

List of node names.

edges List[Tuple[str, str]]

List of edge tuples (source_name, dest_name).

has_tracks bool

Whether track annotations exist.

num_tracks int

Number of unique tracks.

estimated_total_bytes int

Estimated memory for all images.

overlap_frequency float

Fraction of frames with overlapping instances (IoU > 0.2).

node_visibility Optional[Dict[str, float]]

Dict mapping node names to visibility percentage (0-100).

Methods:

Name Description
__repr__

Return repr string.

__str__

Return human-readable summary.

Source code in sleap_nn/config_generator/analyzer.py
@dataclass
class DatasetStats:
    """Statistics extracted from an SLP file for auto-configuration.

    Attributes:
        slp_path: Path to the source SLP file.
        num_labeled_frames: Number of frames with user labels.
        num_videos: Number of video sources.
        max_height: Maximum image height across videos.
        max_width: Maximum image width across videos.
        num_channels: Number of image channels (1=grayscale, 3=RGB).
        max_instances_per_frame: Maximum instances in any single frame.
        avg_instances_per_frame: Average instances per frame.
        max_bbox_size: Maximum bounding box dimension of any instance
            (``max(width, height)`` per instance).
        avg_bbox_size: Average bounding box size, defined as
            ``mean(max(width, height))`` across instances.
        avg_bbox_diagonal: Average bounding box diagonal
            (``mean(sqrt(width**2 + height**2))``). Matches the web app's
            ``slpData.avgAnimalSize`` and is used for default ``max_stride``
            bucket recommendations.
        num_nodes: Number of skeleton nodes.
        num_edges: Number of skeleton edges.
        node_names: List of node names.
        edges: List of edge tuples (source_name, dest_name).
        has_tracks: Whether track annotations exist.
        num_tracks: Number of unique tracks.
        estimated_total_bytes: Estimated memory for all images.
        overlap_frequency: Fraction of frames with overlapping instances (IoU > 0.2).
        node_visibility: Dict mapping node names to visibility percentage (0-100).
    """

    slp_path: str
    num_labeled_frames: int
    num_videos: int
    max_height: int
    max_width: int
    num_channels: int
    max_instances_per_frame: int
    avg_instances_per_frame: float
    max_bbox_size: float
    avg_bbox_size: float
    avg_bbox_diagonal: float
    num_nodes: int
    num_edges: int
    node_names: List[str]
    edges: List[Tuple[str, str]]
    has_tracks: bool
    num_tracks: int
    estimated_total_bytes: int
    total_instances: int = 0
    overlap_frequency: float = 0.0
    node_visibility: Optional[Dict[str, float]] = None

    @property
    def frame_area(self) -> int:
        """Total pixel area of a frame."""
        return self.max_height * self.max_width

    @property
    def animal_to_frame_ratio(self) -> float:
        """Ratio of average animal size to frame dimension (linear, not area).

        This gives a more intuitive percentage - e.g., if an animal bbox is 100px
        and the frame is 1000px, the ratio is 10% (not 1% which area would give).
        """
        if self.max_dimension == 0:
            return 0
        return self.avg_bbox_size / self.max_dimension

    @property
    def is_single_instance(self) -> bool:
        """Whether dataset has only single animals per frame."""
        return self.max_instances_per_frame == 1

    @property
    def is_multi_instance(self) -> bool:
        """Whether dataset has multiple animals per frame."""
        return self.max_instances_per_frame > 1

    @property
    def has_identity(self) -> bool:
        """Whether identity tracking is available."""
        return self.has_tracks and self.num_tracks > 1

    @property
    def is_grayscale(self) -> bool:
        """Whether images are grayscale."""
        return self.num_channels == 1

    @property
    def is_rgb(self) -> bool:
        """Whether images are RGB."""
        return self.num_channels == 3

    @property
    def max_dimension(self) -> int:
        """Maximum image dimension."""
        return max(self.max_height, self.max_width)

    def __str__(self) -> str:
        """Return human-readable summary."""
        lines = [
            f"Dataset: {Path(self.slp_path).name}",
            f"  Labeled frames: {self.num_labeled_frames}",
            f"  Videos: {self.num_videos}",
            f"  Image size: {self.max_width}x{self.max_height} "
            f"({'grayscale' if self.is_grayscale else 'RGB'})",
            f"  Max instances/frame: {self.max_instances_per_frame}",
            f"  Avg instances/frame: {self.avg_instances_per_frame:.1f}",
            f"  Max bbox size: {self.max_bbox_size:.1f}px",
            f"  Avg bbox size: {self.avg_bbox_size:.1f}px",
            f"  Animal size: ~{self.animal_to_frame_ratio * 100:.1f}% of frame",
            f"  Overlap frequency: {self.overlap_frequency * 100:.1f}%",
            f"  Skeleton: {self.num_nodes} nodes, {self.num_edges} edges",
            f"  Tracks: {self.num_tracks if self.has_tracks else 'none'}",
        ]
        return "\n".join(lines)

    def __repr__(self) -> str:
        """Return repr string."""
        return (
            f"DatasetStats("
            f"frames={self.num_labeled_frames}, "
            f"size={self.max_width}x{self.max_height}, "
            f"instances={self.max_instances_per_frame}, "
            f"nodes={self.num_nodes})"
        )

animal_to_frame_ratio property

Ratio of average animal size to frame dimension (linear, not area).

This gives a more intuitive percentage - e.g., if an animal bbox is 100px and the frame is 1000px, the ratio is 10% (not 1% which area would give).

frame_area property

Total pixel area of a frame.

has_identity property

Whether identity tracking is available.

is_grayscale property

Whether images are grayscale.

is_multi_instance property

Whether dataset has multiple animals per frame.

is_rgb property

Whether images are RGB.

is_single_instance property

Whether dataset has only single animals per frame.

max_dimension property

Maximum image dimension.

__repr__()

Return repr string.

Source code in sleap_nn/config_generator/analyzer.py
def __repr__(self) -> str:
    """Return repr string."""
    return (
        f"DatasetStats("
        f"frames={self.num_labeled_frames}, "
        f"size={self.max_width}x{self.max_height}, "
        f"instances={self.max_instances_per_frame}, "
        f"nodes={self.num_nodes})"
    )

__str__()

Return human-readable summary.

Source code in sleap_nn/config_generator/analyzer.py
def __str__(self) -> str:
    """Return human-readable summary."""
    lines = [
        f"Dataset: {Path(self.slp_path).name}",
        f"  Labeled frames: {self.num_labeled_frames}",
        f"  Videos: {self.num_videos}",
        f"  Image size: {self.max_width}x{self.max_height} "
        f"({'grayscale' if self.is_grayscale else 'RGB'})",
        f"  Max instances/frame: {self.max_instances_per_frame}",
        f"  Avg instances/frame: {self.avg_instances_per_frame:.1f}",
        f"  Max bbox size: {self.max_bbox_size:.1f}px",
        f"  Avg bbox size: {self.avg_bbox_size:.1f}px",
        f"  Animal size: ~{self.animal_to_frame_ratio * 100:.1f}% of frame",
        f"  Overlap frequency: {self.overlap_frequency * 100:.1f}%",
        f"  Skeleton: {self.num_nodes} nodes, {self.num_edges} edges",
        f"  Tracks: {self.num_tracks if self.has_tracks else 'none'}",
    ]
    return "\n".join(lines)

ViewType

Bases: Enum

Camera view orientation for augmentation defaults.

Source code in sleap_nn/config_generator/analyzer.py
class ViewType(Enum):
    """Camera view orientation for augmentation defaults."""

    SIDE = "side"
    TOP = "top"
    UNKNOWN = "unknown"

analyze_slp(path, *, user_instances_only=True)

Analyze an SLP file and extract statistics for auto-configuration.

Parameters:

Name Type Description Default
path str

Path to the .slp file.

required
user_instances_only bool

If True, only analyze user-labeled instances.

True

Returns:

Type Description
DatasetStats

DatasetStats object with extracted statistics.

Example

stats = analyze_slp("labels.slp") print(f"Max instances: {stats.max_instances_per_frame}") print(f"Image size: {stats.max_width}x{stats.max_height}")

Source code in sleap_nn/config_generator/analyzer.py
def analyze_slp(
    path: str,
    *,
    user_instances_only: bool = True,
) -> DatasetStats:
    """Analyze an SLP file and extract statistics for auto-configuration.

    Args:
        path: Path to the .slp file.
        user_instances_only: If True, only analyze user-labeled instances.

    Returns:
        DatasetStats object with extracted statistics.

    Example:
        >>> stats = analyze_slp("labels.slp")
        >>> print(f"Max instances: {stats.max_instances_per_frame}")
        >>> print(f"Image size: {stats.max_width}x{stats.max_height}")
    """
    path = str(Path(path).resolve())
    labels = sio.load_slp(path)

    # Basic counts
    num_labeled_frames = len(labels.labeled_frames)
    num_videos = len(labels.videos)

    # Image dimensions
    max_height, max_width = get_max_height_width(labels)
    num_channels = _detect_channels(labels)

    # Instance statistics - use all instances for max count (consistent with get_max_instances)
    max_instances = get_max_instances(labels)

    # Get bbox statistics
    try:
        max_bbox = find_max_instance_bbox_size(labels)
    except Exception:
        max_bbox = 100.0  # Default fallback

    avg_bbox, min_bbox, avg_bbox_diag = _compute_bbox_stats(labels, user_instances_only)
    avg_instances = _compute_avg_instances(labels, user_instances_only)

    # Count total instances - count all instances to be consistent with max_instances
    # (user_instances may be empty if all instances are from predictions/imports)
    total_instances = 0
    for lf in labels.labeled_frames:
        # First try user instances, fall back to all instances if empty
        instances = lf.user_instances if user_instances_only else lf.instances
        non_empty = [inst for inst in instances if not inst.is_empty]
        # If user_instances is empty but there are instances, count all instances
        if not non_empty and user_instances_only:
            non_empty = [inst for inst in lf.instances if not inst.is_empty]
        total_instances += len(non_empty)

    # Compute overlap frequency (only for multi-instance datasets)
    if max_instances > 1:
        overlap_freq = _compute_overlap_frequency(labels, user_instances_only)
    else:
        overlap_freq = 0.0

    # Skeleton info
    skeleton = labels.skeletons[0] if labels.skeletons else None
    node_names = [n.name for n in skeleton.nodes] if skeleton else []
    edges = (
        [(e.source.name, e.destination.name) for e in skeleton.edges]
        if skeleton
        else []
    )

    # Compute node visibility percentages
    node_visibility = _compute_node_visibility(labels, node_names, user_instances_only)

    # Track info
    has_tracks = len(labels.tracks) > 0
    num_tracks = len(labels.tracks)

    # Estimate total bytes for caching
    bytes_per_frame = max_height * max_width * num_channels
    estimated_total_bytes = bytes_per_frame * num_labeled_frames

    return DatasetStats(
        slp_path=path,
        num_labeled_frames=num_labeled_frames,
        num_videos=num_videos,
        max_height=max_height,
        max_width=max_width,
        num_channels=num_channels,
        max_instances_per_frame=max_instances,
        avg_instances_per_frame=avg_instances,
        max_bbox_size=max_bbox,
        avg_bbox_size=avg_bbox,
        avg_bbox_diagonal=avg_bbox_diag,
        num_nodes=len(node_names),
        num_edges=len(edges),
        node_names=node_names,
        edges=edges,
        has_tracks=has_tracks,
        num_tracks=num_tracks,
        estimated_total_bytes=estimated_total_bytes,
        total_instances=total_instances,
        overlap_frequency=overlap_freq,
        node_visibility=node_visibility,
    )