dlc
sleap_io.io.dlc
¶
This module handles direct I/O operations for working with DeepLabCut (DLC) files.
Functions:
Name | Description |
---|---|
is_dlc_file |
Check if file is a DLC CSV file. |
load_dlc |
Load DeepLabCut annotations from CSV file. |
is_dlc_file(filename)
¶
Check if file is a DLC CSV file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filename
|
Union[str, Path]
|
Path to file to check. |
required |
Returns:
Type | Description |
---|---|
bool
|
True if file appears to be a DLC CSV file. |
Source code in sleap_io/io/dlc.py
def is_dlc_file(filename: Union[str, Path]) -> bool:
"""Check if file is a DLC CSV file.
Args:
filename: Path to file to check.
Returns:
True if file appears to be a DLC CSV file.
"""
try:
# Read first few lines as raw text to check for DLC structure
with open(filename, "r") as f:
lines = [f.readline().strip() for _ in range(4)]
# Join all lines to search for DLC patterns
content = "\n".join(lines).lower()
# Check for DLC's characteristic patterns
has_scorer = "scorer" in content
has_coords = "coords" in content
has_xy = "x" in content and "y" in content
has_bodyparts = "bodyparts" in content or any(
part in content for part in ["animal", "individual"]
)
return has_scorer and has_coords and has_xy and has_bodyparts
except Exception:
return False
load_dlc(filename, video_search_paths=None, **kwargs)
¶
Load DeepLabCut annotations from CSV file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
filename
|
Union[str, Path]
|
Path to DLC CSV file. |
required |
video_search_paths
|
Optional[list[Union[str, Path]]]
|
List of paths to search for video files. |
None
|
**kwargs
|
Additional arguments (unused). |
{}
|
Returns:
Type | Description |
---|---|
Labels
|
Labels object with loaded data. |
Source code in sleap_io/io/dlc.py
def load_dlc(
filename: Union[str, Path],
video_search_paths: Optional[list[Union[str, Path]]] = None,
**kwargs,
) -> Labels:
"""Load DeepLabCut annotations from CSV file.
Args:
filename: Path to DLC CSV file.
video_search_paths: List of paths to search for video files.
**kwargs: Additional arguments (unused).
Returns:
Labels object with loaded data.
"""
filename = Path(filename)
# Try reading first few rows to determine format
try:
# Try multi-animal format first (header rows 1-3, skipping scorer row)
df = pd.read_csv(filename, header=[1, 2, 3], nrows=2)
is_multianimal = df.columns[0][0] == "individuals"
except Exception:
# Fall back to single-animal format
is_multianimal = False
# Read full file with appropriate header levels
if is_multianimal:
# Multi-animal format: skip scorer row, use individuals/bodyparts/coords
df = pd.read_csv(filename, header=[1, 2, 3], index_col=0)
else:
# Single-animal format: use scorer/bodyparts/coords
df = pd.read_csv(filename, header=[0, 1, 2], index_col=0)
# Parse structure based on format
if is_multianimal:
skeleton, tracks = _parse_multi_animal_structure(df)
else:
skeleton = _parse_single_animal_structure(df)
tracks = []
# First, group all image paths by their video directory
video_image_paths = {}
frame_map = {} # Maps image path to frame index
for idx in df.index:
img_path = str(idx)
frame_idx = _extract_frame_index(img_path)
frame_map[img_path] = frame_idx
# Extract video name from path
# e.g., "labeled-data/video/img000.png" -> "video"
path_parts = Path(img_path).parts
if len(path_parts) >= 2 and path_parts[0] == "labeled-data":
video_name = path_parts[1]
else:
video_name = Path(img_path).parent.name or "default"
if video_name not in video_image_paths:
video_image_paths[video_name] = []
video_image_paths[video_name].append(img_path)
# Create one Video object per video directory
videos = {}
for video_name, image_paths in video_image_paths.items():
# Sort image paths to ensure consistent ordering
sorted_paths = sorted(image_paths, key=lambda p: frame_map[p])
# Find the actual image files
actual_image_files = []
for img_path in sorted_paths:
# First try the full path from CSV
full_path = filename.parent / img_path
if full_path.exists():
actual_image_files.append(str(full_path))
else:
# Try just the filename in the same directory as the CSV
img_name = Path(img_path).name
simple_path = filename.parent / img_name
if simple_path.exists():
actual_image_files.append(str(simple_path))
else:
# Try going up one directory from CSV location
# (CSV in subdir references parent/subdir/img.png)
parent_path = filename.parent.parent / img_path
if parent_path.exists():
actual_image_files.append(str(parent_path))
# Only create video if we found actual images
if actual_image_files:
videos[video_name] = Video.from_filename(actual_image_files)
# Parse the actual data rows and create labeled frames
labeled_frames = []
for idx, row in df.iterrows():
# Get image path from index
img_path = str(idx)
frame_idx = _extract_frame_index(img_path)
# Determine which video this frame belongs to
path_parts = Path(img_path).parts
if len(path_parts) >= 2 and path_parts[0] == "labeled-data":
video_name = path_parts[1]
else:
video_name = Path(img_path).parent.name or "default"
# Skip if we don't have a video for this frame
if video_name not in videos:
continue
# Parse instances for this frame
if is_multianimal:
instances = _parse_multi_animal_row(row, skeleton, tracks)
else:
instances = _parse_single_animal_row(row, skeleton)
if instances:
# Get the index of this image within its video
sorted_video_paths = sorted(
video_image_paths[video_name], key=lambda p: frame_map[p]
)
video_frame_idx = sorted_video_paths.index(img_path)
labeled_frames.append(
LabeledFrame(
video=videos[video_name],
frame_idx=video_frame_idx,
instances=instances,
)
)
unique_videos = list(videos.values())
return Labels(
labeled_frames=labeled_frames,
videos=unique_videos,
tracks=tracks,
skeletons=[skeleton] if skeleton.nodes else [],
)