Skip to content

lf_labels_utils

sleap.sleap_io_adaptors.lf_labels_utils

Standalone utility functions for working with Labels and LabeledFrame objects.

Functions:

Name Description
add_suggestion

Add a suggestion to the labels dataset.

clear_suggestion

Delete all suggestions.

find_first

Find the first occurrence of a matching labeled frame.

find_last

Find the last occurrence of a matching labeled frame.

find_path_using_paths

Find a file in the given search paths.

find_suggestion

Return the suggestion for the given (video, frame_idx) or None.

find_track_occupancy

Get instances for a given video, track, and range of frames.

get_instances_to_show

Return a list of instances to show in GUI for this frame.

get_labeled_frame_count

Return count of frames matching video/filter.

get_next_suggestion

Return a (video, frame_idx) tuple seeking from given frame.

get_template_instance_points

Get template instance points for a skeleton.

get_track_occupancy

Get track occupancy information for a specific video.

get_unused_predictions

Return a list of "unused" PredictedInstance objects in frame.

get_video_suggestions

Get suggested frame indices for a specific video.

iterate_labeled_frames

Return an iterator over lfs in a video with start pos (opt) and order control.

labeled_frame_find

Find instances in a labeled frame that match the given track.

labels_add_instance

Add an instance to a labeled frame.

labels_add_video

Add a video to the Labels object.

labels_all_instances

Get all instances as a list for backward compatibility.

labels_append_suggestions

Append suggestions to the Labels object.

labels_clear_suggestions

Clear all suggestions from labels for backward compatibility.

labels_copy

Create a copy of the Labels object.

labels_frames

Get labeled frames, optionally filtered by video.

labels_get

Get labeled frames for backward compatibility.

labels_get_labels_attr

Get labeled frames for backward compatibility.

labels_get_nodes

Get skeleton nodes for backward compatibility.

labels_get_suggestions

Get all suggestions from labels for backward compatibility.

labels_load_file

Load a Labels object from file.

labels_pop

Remove and return a labeled frame at the given index.

labels_remove_frame

Remove a single labeled frame from the Labels object.

make_video_callback

Adapter function for callback function to finding missing video.

merge_nodes

Merge two nodes and update data accordingly.

merge_nodes_data

Copy point data from one node to another.

remove_all_tracks

Remove all tracks from the labels dataset and update all related instances.

remove_frames

Remove a list of frames from the labels dataset.

remove_instance

Remove an instance from a labeled frame and update all related instances.

remove_track

Remove a track from the labels dataset and update all related instances.

remove_unused_tracks

Remove all tracks from the labels dataset that are not used by any instances.

remove_video

Remove a video from the labels dataset and update all related instances.

track_set_instance

Set track on given instance, updating occupancy.

track_swap

Swap track assignment for instances in two tracks.

add_suggestion(labels, video, frame_idx)

Add a suggestion to the labels dataset.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
239
240
241
def add_suggestion(labels, video, frame_idx):
    """Add a suggestion to the labels dataset."""
    labels.suggestions.append(SuggestionFrame(video=video, frame_idx=frame_idx))

clear_suggestion(labels)

Delete all suggestions.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1168
1169
1170
def clear_suggestion(labels: Labels):
    """Delete all suggestions."""
    labels.suggestions.clear()

find_first(labels, video, frame_idx=None, use_cache=False)

Find the first occurrence of a matching labeled frame.

This function recreates labels.find_first(video, frame_idx, use_cache) from the original SLEAP codebase.

Matches on frames for the given video and/or frame index.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames

required
video

A Video instance that is associated with the labeled frames

required
frame_idx

An integer specifying the frame index within the video (optional)

None
use_cache bool

Boolean that determines whether to use cache. If True, use the labels data cache, else loop through all labels to search.

False

Returns:

Type Description

First LabeledFrame that matches the criteria or None if none were found.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
def find_first(labels, video, frame_idx=None, use_cache: bool = False):
    """Find the first occurrence of a matching labeled frame.

    This function recreates labels.find_first(video, frame_idx, use_cache)
    from the original SLEAP codebase.

    Matches on frames for the given video and/or frame index.

    Args:
        labels: A Labels object containing labeled frames
        video: A Video instance that is associated with the labeled frames
        frame_idx: An integer specifying the frame index within the video (optional)
        use_cache: Boolean that determines whether to use cache. If True, use the labels
            data cache, else loop through all labels to search.

    Returns:
        First LabeledFrame that matches the criteria or None if none were found.
    """
    if use_cache and hasattr(labels, "find"):
        # Use cache if available
        label = labels.find(video=video, frame_idx=frame_idx)
        return None if len(label) == 0 else label[0]
    else:
        # Check if video is in labels
        if hasattr(labels, "videos") and video in labels.videos:
            # Loop through all labels
            for label in labels:
                if (
                    hasattr(label, "video")
                    and label.video == video
                    and (
                        frame_idx is None
                        or (
                            hasattr(label, "frame_idx") and label.frame_idx == frame_idx
                        )
                    )
                ):
                    return label
        return None

find_last(labels, video, frame_idx=None)

Find the last occurrence of a matching labeled frame.

This function recreates the functionality of labels.find_last(video, frame_idx) from the original SLEAP codebase.

Matches on frames for the given video and/or frame index.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames

required
video

A Video instance that is associated with the labeled frames

required
frame_idx

An integer specifying the frame index within the video (optional)

None

Returns:

Type Description

Last LabeledFrame that matches the criteria or None if none were found.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
def find_last(labels, video, frame_idx=None):
    """Find the last occurrence of a matching labeled frame.

    This function recreates the functionality of labels.find_last(video, frame_idx)
    from the original SLEAP codebase.

    Matches on frames for the given video and/or frame index.

    Args:
        labels: A Labels object containing labeled frames
        video: A Video instance that is associated with the labeled frames
        frame_idx: An integer specifying the frame index within the video (optional)

    Returns:
        Last LabeledFrame that matches the criteria or None if none were found.
    """
    # Check if video is in labels
    if hasattr(labels, "videos") and video in labels.videos:
        # Loop through all labels in reverse order
        for label in reversed(list(labels)):
            if (
                hasattr(label, "video")
                and label.video == video
                and (
                    frame_idx is None
                    or (hasattr(label, "frame_idx") and label.frame_idx == frame_idx)
                )
            ):
                return label
    return None

find_path_using_paths(filename, search_paths)

Find a file in the given search paths.

Parameters:

Name Type Description Default
filename str

The filename to search for.

required
search_paths List[str]

List of directories to search in.

required

Returns:

Type Description
str

The found path or the original filename if not found.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def find_path_using_paths(filename: str, search_paths: List[str]) -> str:
    """Find a file in the given search paths.

    Args:
        filename: The filename to search for.
        search_paths: List of directories to search in.

    Returns:
        The found path or the original filename if not found.
    """
    filename_path = Path(filename)

    for search_path in search_paths:
        search_path_obj = Path(search_path)
        if search_path_obj.is_dir():
            potential_path = search_path_obj / filename_path.name
            if potential_path.exists():
                return str(potential_path)

    return filename

find_suggestion(labels, video, frame_idx)

Return the suggestion for the given (video, frame_idx) or None.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
915
916
917
918
919
920
921
922
923
924
925
def find_suggestion(labels: Labels, video, frame_idx):
    """Return the suggestion for the given (video, frame_idx) or None."""
    match = [
        item
        for item in labels.suggestions
        if item.video == video and item.frame_idx == frame_idx
    ]
    if match:
        return match[0]

    return None

find_track_occupancy(labels, video, track, frame_range=None)

Get instances for a given video, track, and range of frames.

Parameters:

Name Type Description Default
video Video

the Video

required
track Union[Track, int]

the Track or int ("pseudo-track" index to instance list)

required
frame_range optional

If specified, only return instances on frames in range. If None, return all instances for given track.

None

Returns:

Type Description
List[Instance]

List of :class:Instance objects.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
def find_track_occupancy(
    labels: Labels, video: Video, track: Union[Track, int], frame_range=None
) -> List[Instance]:
    """Get instances for a given video, track, and range of frames.

    Args:
        video: the `Video`
        track: the `Track` or int ("pseudo-track" index to instance list)
        frame_range (optional):
            If specified, only return instances on frames in range.
            If None, return all instances for given track.

    Returns:
        List of :class:`Instance` objects.
    """
    frame_range = range(*frame_range) if type(frame_range) == tuple else frame_range

    def does_track_match(inst, tr, labeled_frame):
        match = False
        if type(tr) == Track and inst.track is tr:
            match = True
        elif (
            type(tr) == int
            and labeled_frame.instances.index(inst) == tr
            and inst.track is None
        ):
            match = True
        return match

    track_frame_inst = [
        instance
        for lf in labels.find(video)
        for instance in lf.instances
        if does_track_match(instance, track, lf)
        and (frame_range is None or lf.frame_idx in frame_range)
    ]

    return track_frame_inst

get_instances_to_show(labeled_frame)

Return a list of instances to show in GUI for this frame.

This function recreates the functionality of labeled_frame.instances_to_show from the original SLEAP codebase.

This list will not include any predicted instances for which there's a corresponding regular instance.

Parameters:

Name Type Description Default
labeled_frame

A LabeledFrame object containing instances

required

Returns:

Type Description
List

List of instances to show in GUI.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
def get_instances_to_show(labeled_frame) -> List:
    """Return a list of instances to show in GUI for this frame.

    This function recreates the functionality of labeled_frame.instances_to_show
    from the original SLEAP codebase.

    This list will not include any predicted instances for which
    there's a corresponding regular instance.

    Args:
        labeled_frame: A LabeledFrame object containing instances

    Returns:
        List of instances to show in GUI.
    """
    unused_predictions = get_unused_predictions(labeled_frame)

    # Check if labeled_frame has instances attribute
    if not hasattr(labeled_frame, "instances"):
        return []

    instances = labeled_frame.instances if hasattr(labeled_frame, "instances") else []

    inst_to_show = [
        inst
        for inst in instances
        if not hasattr(inst, "from_predicted") or inst in unused_predictions
    ]

    return inst_to_show

get_labeled_frame_count(labels, video=None, filter='')

Return count of frames matching video/filter.

This function recreates labels.get_labeled_frame_count(video, filter) from the original SLEAP codebase.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames

required
video

Optional Video object to filter by. If None, counts all videos

None
filter str

Filter string. Must be one of: "", "user", "predicted" - "": All labeled frames - "user": Only frames with user-labeled instances - "predicted": Only frames with predicted instances

''

Returns:

Type Description
int

Count of frames matching the criteria

Raises:

Type Description
ValueError

If filter is not one of the valid options

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
def get_labeled_frame_count(labels, video=None, filter: str = "") -> int:
    """Return count of frames matching video/filter.

    This function recreates labels.get_labeled_frame_count(video, filter)
    from the original SLEAP codebase.

    Args:
        labels: A Labels object containing labeled frames
        video: Optional Video object to filter by. If None, counts all videos
        filter: Filter string. Must be one of: "", "user", "predicted"
            - "": All labeled frames
            - "user": Only frames with user-labeled instances
            - "predicted": Only frames with predicted instances

    Returns:
        Count of frames matching the criteria

    Raises:
        ValueError: If filter is not one of the valid options
    """
    if filter not in ("", "user", "predicted"):
        raise ValueError(f"get_labeled_frame_count() invalid filter: {filter}")

    # Get all labeled frames
    if hasattr(labels, "labeled_frames"):
        all_frames = labels.labeled_frames
    elif hasattr(labels, "__iter__"):
        # If labels is iterable, use it directly
        all_frames = list(labels)
    else:
        return 0

    # Apply video filter
    if video is not None:
        frames = [lf for lf in all_frames if hasattr(lf, "video") and lf.video == video]
    else:
        frames = all_frames

    # Apply type filter
    if filter == "":
        # All labeled frames
        return len(frames)
    elif filter == "user":
        # Only frames with user instances
        return len(
            [
                lf
                for lf in frames
                if hasattr(lf, "has_user_instances") and lf.has_user_instances
            ]
        )
    elif filter == "predicted":
        # Only frames with predicted instances
        return len(
            [
                lf
                for lf in frames
                if hasattr(lf, "has_predicted_instances") and lf.has_predicted_instances
            ]
        )

    return 0

get_next_suggestion(labels, video, frame_idx, seek_direction=1)

Return a (video, frame_idx) tuple seeking from given frame.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
def get_next_suggestion(labels: Labels, video, frame_idx, seek_direction=1):
    """Return a (video, frame_idx) tuple seeking from given frame."""
    # make sure we have valid seek_direction
    if seek_direction not in (-1, 1):
        raise ValueError("Invalid seek_direction. Use -1 or 1.")

    # make sure the video belongs to the labels object
    if video not in labels.videos:
        return None

    all_suggestions = labels.suggestions

    # If we are currently on a suggestion, then follow order of list
    match = find_suggestion(labels, video, frame_idx)
    if match is not None:
        suggestion_idx = all_suggestions.index(match)
        new_idx = (suggestion_idx + seek_direction) % len(all_suggestions)
        return all_suggestions[new_idx]

    # Otherwise, find the prev/next suggestion sorted by frame order...

    # Look for next (or previous) suggestion in current video.
    if seek_direction == 1:
        frame_suggestion = min(
            (i for i in get_video_suggestions(labels, video) if i > frame_idx),
            default=None,
        )
    else:
        frame_suggestion = max(
            (i for i in get_video_suggestions(labels, video) if i < frame_idx),
            default=None,
        )
        if frame_suggestion is not None:
            return (video, frame_suggestion)

    if frame_suggestion is not None:
        return find_suggestion(labels, video, frame_suggestion)

    # If we did not find suggestion in current video,
    # then we want earliest frame in next video with suggestions

    next_video_idx = (labels.videos.index(video) + seek_direction) % len(labels.videos)
    video = labels.videos[next_video_idx]
    if seek_direction == 1:
        frame_suggestion = min(
            (i for i in get_video_suggestions(labels, video)), default=None
        )
    else:
        frame_suggestion = max(
            (i for i in get_video_suggestions(labels, video)), default=None
        )

    return find_suggestion(labels, video, frame_suggestion)

get_template_instance_points(labels, skeleton)

Get template instance points for a skeleton.

This function recreates labels.get_template_instance_points(skeleton) from the original SLEAP codebase.

Parameters:

Name Type Description Default
labels Labels

A Labels object containing labeled frames and instances

required
skeleton Skeleton

A Skeleton object to get template points for

required

Returns:

Type Description

numpy array of template points for the skeleton

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
def get_template_instance_points(labels: Labels, skeleton: Skeleton):
    """Get template instance points for a skeleton.

    This function recreates labels.get_template_instance_points(skeleton)
    from the original SLEAP codebase.

    Args:
        labels: A Labels object containing labeled frames and instances
        skeleton: A Skeleton object to get template points for

    Returns:
        numpy array of template points for the skeleton
    """
    import itertools
    import numpy as np

    # Check if labels has labeled_frames attribute
    if not hasattr(labels, "labeled_frames"):
        print("Labels object has no labeled_frames attribute")
        return None

    # Check if labeled_frame list is empty
    if not labels.labeled_frames:
        # No labeled frames so use force-directed graph layout
        try:
            import networkx as nx
            from sleap.sleap_io_adaptors.skeleton_utils import to_graph

            # Create graph from skeleton and get spring layout
            G = to_graph(skeleton)
            node_positions = nx.spring_layout(G=G, scale=50)

            # Create template points from node positions
            template_points = np.stack(
                [
                    (
                        node_positions[node]
                        if node in node_positions
                        else np.random.randint(0, 50, size=2)
                    )
                    for node in skeleton.nodes
                ]
            )

            return template_points

        except ImportError:
            # Fallback if networkx is not available
            template_points = np.random.randint(0, 50, size=(len(skeleton.nodes), 2))
            return template_points

    # Check if there are any instances
    if not hasattr(labels, "instances") or not instances(labels):
        # No instances, use fallback
        template_points = np.random.randint(0, 50, size=(len(skeleton.nodes), 2))
        return template_points

    # Get first 1000 instances for this skeleton
    try:
        from sleap.info import align

        # Get instances for this skeleton
        skeleton_instances = []
        for instance in itertools.islice(
            instances(labels=labels, skeleton=skeleton), 1000
        ):
            if hasattr(instance, "points") and instance.points is not None:
                skeleton_instances.append(instance)

        if skeleton_instances:
            # Get template points from aligned instances
            template_points = align.get_template_points_array(skeleton_instances)
            return template_points
        else:
            # No valid instances, use fallback
            template_points = np.random.randint(0, 50, size=(len(skeleton.nodes), 2))
            return template_points

    except ImportError:
        # Fallback if sleap.info.align is not available
        template_points = np.random.randint(0, 50, size=(len(skeleton.nodes), 2))
        return template_points

get_track_occupancy(labels, video)

Get track occupancy information for a specific video.

This function recreates the functionality of labels.get_track_occupancy(video) from the original SLEAP codebase.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames and tracks

required
video

A Video object to get track occupancy for

required

Returns:

Type Description

Dict mapping Track objects to their occupancy information (frame ranges)

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def get_track_occupancy(labels, video):
    """Get track occupancy information for a specific video.

    This function recreates the functionality of labels.get_track_occupancy(video)
    from the original SLEAP codebase.

    Args:
        labels: A Labels object containing labeled frames and tracks
        video: A Video object to get track occupancy for

    Returns:
        Dict mapping Track objects to their occupancy information (frame ranges)
    """
    track_occupancy = {}

    # Get all labeled frames for this video
    labeled_frames = labels.find(video) if hasattr(labels, "find") else []

    # Build track occupancy dictionary
    for lf in labeled_frames:
        for instance in lf.instances:
            track = instance.track.name if instance.track is not None else None
            if track not in track_occupancy:
                track_occupancy[track] = []

            # Add this frame to the track's occupancy
            track_occupancy[track].append(lf.frame_idx)

    # Convert frame lists to sorted ranges
    for track in track_occupancy:
        if track_occupancy[track]:
            # Sort frame indices
            frames = sorted(track_occupancy[track])

            # Create ranges (consecutive frames)
            ranges = []
            start = frames[0]
            prev = frames[0]

            for frame in frames[1:]:
                if frame != prev + 1:
                    # Gap found, end current range
                    ranges.append((start, prev + 1))
                    start = frame
                prev = frame

            # Add final range
            ranges.append((start, prev + 1))

            track_occupancy[track] = SimpleRange(ranges)

    return track_occupancy

get_unused_predictions(labeled_frame)

Return a list of "unused" PredictedInstance objects in frame.

This function recreates the functionality of labeled_frame.unused_predictions from the original SLEAP codebase.

This is all the PredictedInstance objects which do not have a corresponding Instance in the same track in frame.

Parameters:

Name Type Description Default
labeled_frame

A LabeledFrame object containing instances

required

Returns:

Type Description
List

List of unused PredictedInstance objects

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def get_unused_predictions(labeled_frame) -> List:
    """Return a list of "unused" PredictedInstance objects in frame.

    This function recreates the functionality of labeled_frame.unused_predictions
    from the original SLEAP codebase.

    This is all the PredictedInstance objects which do not have
    a corresponding Instance in the same track in frame.

    Args:
        labeled_frame: A LabeledFrame object containing instances

    Returns:
        List of unused PredictedInstance objects
    """
    unused_predictions = []

    # Check if labeled_frame has instances attribute
    if not hasattr(labeled_frame, "instances"):
        return unused_predictions

    # Get all instances from the frame
    instances = labeled_frame.instances if hasattr(labeled_frame, "instances") else []

    any_tracks = [
        inst.track
        for inst in instances
        if hasattr(inst, "track") and inst.track is not None
    ]

    if len(any_tracks):
        # Use tracks to determine which predicted instances have been used
        used_tracks = [
            inst.track
            for inst in instances
            if hasattr(inst, "track")
            and inst.track is not None
            and not hasattr(inst, "from_predicted")
        ]
        unused_predictions = [
            inst
            for inst in instances
            if hasattr(inst, "track")
            and inst.track not in used_tracks
            and hasattr(inst, "from_predicted")
        ]
    else:
        # Use from_predicted to determine which predicted instances have been used
        used_instances = [
            inst.from_predicted
            for inst in instances
            if hasattr(inst, "from_predicted") and inst.from_predicted is not None
        ]
        unused_predictions = [
            inst
            for inst in instances
            if hasattr(inst, "from_predicted") and inst not in used_instances
        ]

    return unused_predictions

get_video_suggestions(labels, video, user_labeled=True)

Get suggested frame indices for a specific video.

This function recreates the functionality of labels.get_video_suggestions(video) from the original SLEAP codebase.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames and suggestions

required
video

A Video object to get suggestions for

required
user_labeled bool

If True (the default), return frame indices for suggestions that already have user labels. If False, only suggestions with no user labeled instances will be returned.

True

Returns:

Type Description
List[int]

List of frame indices that are suggested for the specified video.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def get_video_suggestions(labels, video, user_labeled: bool = True) -> List[int]:
    """Get suggested frame indices for a specific video.

    This function recreates the functionality of labels.get_video_suggestions(video)
    from the original SLEAP codebase.

    Args:
        labels: A Labels object containing labeled frames and suggestions
        video: A Video object to get suggestions for
        user_labeled: If True (the default), return frame indices for suggestions
            that already have user labels. If False, only suggestions with no user
            labeled instances will be returned.

    Returns:
        List of frame indices that are suggested for the specified video.
    """
    frame_indices = []

    # Check if labels has a suggestions attribute
    if not hasattr(labels, "suggestions"):
        return frame_indices

    # Get suggestions for this video
    for suggestion in labels.suggestions:
        if suggestion.video == video:
            fidx = suggestion.frame_idx

            # If user_labeled is False, skip suggestions that already have user labels
            if not user_labeled:
                lf = labels.find((video, fidx))
                if (
                    lf is not None
                    and hasattr(lf, "has_user_instances")
                    and lf.has_user_instances
                ):
                    continue

            frame_indices.append(fidx)

    return frame_indices

iterate_labeled_frames(labels, video, from_frame_idx=-1, reverse=False)

Return an iterator over lfs in a video with start pos (opt) and order control.

This function recreates Labels.frames() from the original SLEAP codebase.

Parameters:

Name Type Description Default
labels

A Labels object containing labeled frames

required
video

A Video object that is associated with the project

required
from_frame_idx int

The frame index from which to start (default: -1 for beginning)

-1
reverse bool

Whether to iterate over frames in reverse order (default: False)

False

Yields:

Type Description

LabeledFrame objects for the specified video

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
def iterate_labeled_frames(
    labels, video, from_frame_idx: int = -1, reverse: bool = False
):
    """Return an iterator over lfs in a video with start pos (opt) and order control.

    This function recreates Labels.frames() from the original SLEAP codebase.

    Args:
        labels: A Labels object containing labeled frames
        video: A Video object that is associated with the project
        from_frame_idx: The frame index from which to start (default: -1 for beginning)
        reverse: Whether to iterate over frames in reverse order (default: False)

    Yields:
        LabeledFrame objects for the specified video
    """
    # Get all labeled frames for this video
    labeled_frames = labels.find(video) if hasattr(labels, "find") else []

    if not labeled_frames:
        return

    # Extract frame indices and sort them
    frame_idxs = sorted(
        [lf.frame_idx for lf in labeled_frames if hasattr(lf, "frame_idx")]
    )

    if not frame_idxs:
        return

    # Handle the case where from_frame_idx is -1 (start from beginning)
    if from_frame_idx == -1:
        if reverse:
            frame_idxs = frame_idxs[::-1]  # Reverse the list
        # Use the frame_idxs as is for forward direction

    else:
        # Find the next frame index after/before the specified frame
        if not reverse:
            # Forward direction: find next frame after from_frame_idx
            next_frame_idx = None
            for idx in frame_idxs:
                if idx > from_frame_idx:
                    next_frame_idx = idx
                    break
            if next_frame_idx is None:
                next_frame_idx = frame_idxs[0]  # Wrap to beginning
        else:
            # Reverse direction: find previous frame before from_frame_idx
            next_frame_idx = None
            for idx in reversed(frame_idxs):
                if idx < from_frame_idx:
                    next_frame_idx = idx
                    break
            if next_frame_idx is None:
                next_frame_idx = frame_idxs[-1]  # Wrap to end

        # Find the position of the next frame in the list
        try:
            cut_list_idx = frame_idxs.index(next_frame_idx)
        except ValueError:
            # If not found, use original order
            if reverse:
                frame_idxs = frame_idxs[::-1]
            return

        # Reorder the list to start from the specified position
        if reverse:
            # For reverse, we need to handle the reordering differently
            reordered = frame_idxs[cut_list_idx:] + frame_idxs[:cut_list_idx]
            frame_idxs = reordered[::-1]  # Reverse the reordered list
        else:
            # For forward, just reorder normally
            frame_idxs = frame_idxs[cut_list_idx:] + frame_idxs[:cut_list_idx]

    # Create a mapping from frame_idx to LabeledFrame for quick lookup
    frame_map = {lf.frame_idx: lf for lf in labeled_frames if hasattr(lf, "frame_idx")}

    # Yield the frames in the order specified by frame_idxs
    for idx in frame_idxs:
        if idx in frame_map:
            yield frame_map[idx]

labeled_frame_find(labeled_frame, track=None)

Find instances in a labeled frame that match the given track.

This provides backward compatibility for the missing LabeledFrame.find() method. In sleap-io, we need to manually search through instances to find matching tracks.

Parameters:

Name Type Description Default
labeled_frame LabeledFrame

LabeledFrame to search in

required
track Track

Track to search for

None

Returns:

Type Description

List of instances that match the track, or empty list if none found

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
def labeled_frame_find(labeled_frame: LabeledFrame, track: Track = None):
    """Find instances in a labeled frame that match the given track.

    This provides backward compatibility for the missing LabeledFrame.find() method.
    In sleap-io, we need to manually search through instances to find matching tracks.

    Args:
        labeled_frame: LabeledFrame to search in
        track: Track to search for

    Returns:
        List of instances that match the track, or empty list if none found
    """
    if track is None:
        return list(labeled_frame.instances)

    matching_instances = []
    for instance in labeled_frame.instances:
        if instance.track == track:
            matching_instances.append(instance)

    return matching_instances

labels_add_instance(labels, frame, instance)

Add an instance to a labeled frame.

This provides backward compatibility for the missing add_instance() method. In sleap-io, we manually add instances to the frame's instances list.

Parameters:

Name Type Description Default
labels Labels

Labels object (for consistency with legacy API, but not used)

required
frame LabeledFrame

LabeledFrame to add instance to

required
instance

Instance to add to the frame

required
Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
def labels_add_instance(labels: Labels, frame: LabeledFrame, instance):
    """Add an instance to a labeled frame.

    This provides backward compatibility for the missing add_instance() method.
    In sleap-io, we manually add instances to the frame's instances list.

    Args:
        labels: Labels object (for consistency with legacy API, but not used)
        frame: LabeledFrame to add instance to
        instance: Instance to add to the frame
    """
    # In sleap-io, instances are stored in the LabeledFrame directly
    if hasattr(frame, "instances"):
        frame.instances.append(instance)
    else:
        # Fallback if instances is not a list
        frame.instances = list(frame.instances) + [instance]

labels_add_video(labels, video)

Add a video to the Labels object.

This provides backward compatibility for the missing add_video() method.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1263
1264
1265
1266
1267
1268
1269
def labels_add_video(labels: Labels, video: Video):
    """Add a video to the Labels object.

    This provides backward compatibility for the missing add_video() method.
    """
    if video not in labels.videos:
        labels.videos.append(video)

labels_all_instances(labels)

Get all instances as a list for backward compatibility.

This provides backward compatibility for the missing all_instances attribute. In sleap-io, labels.instances is a generator, but legacy SLEAP expects a list-like object.

Parameters:

Name Type Description Default
labels Labels

Labels object to get all instances from

required

Returns:

Type Description

List of all Instance objects from all labeled frames

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
def labels_all_instances(labels: Labels):
    """Get all instances as a list for backward compatibility.

    This provides backward compatibility for the missing all_instances attribute.
    In sleap-io, labels.instances is a generator, but legacy SLEAP expects
    a list-like object.

    Args:
        labels: Labels object to get all instances from

    Returns:
        List of all Instance objects from all labeled frames
    """
    return list(labels.instances)

labels_append_suggestions(labels, suggestions)

Append suggestions to the Labels object.

This provides backward compatibility for the missing append_suggestions() method. In sleap-io, suggestions are stored as a list that can be extended directly.

Parameters:

Name Type Description Default
labels Labels

Labels object to append suggestions to

required
suggestions

List of SuggestionFrame objects to append

required
Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
def labels_append_suggestions(labels: Labels, suggestions):
    """Append suggestions to the Labels object.

    This provides backward compatibility for the missing append_suggestions() method.
    In sleap-io, suggestions are stored as a list that can be extended directly.

    Args:
        labels: Labels object to append suggestions to
        suggestions: List of SuggestionFrame objects to append
    """
    if hasattr(suggestions, "__iter__"):
        labels.suggestions.extend(suggestions)
    else:
        labels.suggestions.append(suggestions)

labels_clear_suggestions(labels)

Clear all suggestions from labels for backward compatibility.

This provides backward compatibility for the missing clear_suggestions() method. In sleap-io, suggestions are stored as a list that can be cleared directly.

Parameters:

Name Type Description Default
labels Labels

Labels object to clear suggestions from

required
Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
def labels_clear_suggestions(labels: Labels):
    """Clear all suggestions from labels for backward compatibility.

    This provides backward compatibility for the missing clear_suggestions() method.
    In sleap-io, suggestions are stored as a list that can be cleared directly.

    Args:
        labels: Labels object to clear suggestions from
    """
    labels.suggestions.clear()

labels_copy(labels)

Create a copy of the Labels object.

This provides backward compatibility for the missing copy() method. Uses copy.deepcopy() which should be handled gracefully by sleap-io.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1254
1255
1256
1257
1258
1259
1260
def labels_copy(labels: Labels) -> Labels:
    """Create a copy of the Labels object.

    This provides backward compatibility for the missing copy() method.
    Uses copy.deepcopy() which should be handled gracefully by sleap-io.
    """
    return copy.deepcopy(labels)

labels_frames(labels, video=None)

Get labeled frames, optionally filtered by video.

This provides backward compatibility for the missing frames() method.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1331
1332
1333
1334
1335
1336
1337
1338
1339
def labels_frames(labels: Labels, video: Video = None):
    """Get labeled frames, optionally filtered by video.

    This provides backward compatibility for the missing frames() method.
    """
    if video is None:
        return labels.labeled_frames
    else:
        return labels.find(video)

labels_get(labels, video_and_frame_or_video, frame_idx=None, **kwargs)

Get labeled frames for backward compatibility.

This provides backward compatibility for the missing get() method. Handles both tuple format (video, frame_idx) and separate video, frame_idx args.

Parameters:

Name Type Description Default
labels Labels

Labels object to search

required
video_and_frame_or_video

Either a (Video, frame_idx) tuple or a Video object

required
frame_idx

Frame index (when first arg is Video)

None
**kwargs

Additional arguments like use_cache (ignored for sleap-io compatibility)

{}

Returns:

Type Description

Single LabeledFrame if found, None otherwise (when frame_idx specified) List of LabeledFrame objects for video (when frame_idx not specified)

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
def labels_get(labels: Labels, video_and_frame_or_video, frame_idx=None, **kwargs):
    """Get labeled frames for backward compatibility.

    This provides backward compatibility for the missing get() method.
    Handles both tuple format (video, frame_idx) and separate video, frame_idx args.

    Args:
        labels: Labels object to search
        video_and_frame_or_video: Either a (Video, frame_idx) tuple or a Video object
        frame_idx: Frame index (when first arg is Video)
        **kwargs: Additional arguments like use_cache
            (ignored for sleap-io compatibility)

    Returns:
        Single LabeledFrame if found, None otherwise (when frame_idx specified)
        List of LabeledFrame objects for video (when frame_idx not specified)
    """
    # Handle tuple format: labels.get((video, frame_idx))
    if (
        isinstance(video_and_frame_or_video, tuple)
        and len(video_and_frame_or_video) == 2
    ):
        video, frame_idx = video_and_frame_or_video
    else:
        video = video_and_frame_or_video

    # Use the existing Labels.find method from sleap-io
    if frame_idx is not None:
        matches = labels.find(video, frame_idx=frame_idx)
        return matches[0] if matches else None
    else:
        return labels.find(video)

labels_get_labels_attr(labels)

Get labeled frames for backward compatibility.

Maps labels.labels to labels.labeled_frames

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1323
1324
1325
1326
1327
1328
def labels_get_labels_attr(labels: Labels):
    """Get labeled frames for backward compatibility.

    Maps labels.labels to labels.labeled_frames
    """
    return labels.labeled_frames

labels_get_nodes(labels)

Get skeleton nodes for backward compatibility.

Maps labels.nodes to labels.skeleton.nodes or labels.skeletons[0].nodes

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1313
1314
1315
1316
1317
1318
1319
1320
def labels_get_nodes(labels: Labels):
    """Get skeleton nodes for backward compatibility.

    Maps labels.nodes to labels.skeleton.nodes or labels.skeletons[0].nodes
    """
    if labels.skeletons:
        return labels.skeletons[0].nodes
    return []

labels_get_suggestions(labels)

Get all suggestions from labels for backward compatibility.

This provides backward compatibility for the missing get_suggestions() method. In sleap-io, suggestions are stored directly as an attribute.

Parameters:

Name Type Description Default
labels Labels

Labels object to get suggestions from

required

Returns:

Type Description

List of SuggestionFrame objects

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
def labels_get_suggestions(labels: Labels):
    """Get all suggestions from labels for backward compatibility.

    This provides backward compatibility for the missing get_suggestions() method.
    In sleap-io, suggestions are stored directly as an attribute.

    Args:
        labels: Labels object to get suggestions from

    Returns:
        List of SuggestionFrame objects
    """
    return getattr(labels, "suggestions", [])

labels_load_file(filename, **kwargs)

Load a Labels object from file.

This provides backward compatibility for the missing static load_file() method. Handles video_search parameter that sleap-io doesn't support.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
def labels_load_file(filename: str, **kwargs) -> Labels:
    """Load a Labels object from file.

    This provides backward compatibility for the missing static load_file() method.
    Handles video_search parameter that sleap-io doesn't support.
    """
    # Extract video_search parameter if present
    video_search = kwargs.pop("video_search", None)

    if video_search is not None:
        # Use existing video search function
        return load_labels_video_search(filename, video_search)
    else:
        # Standard load without video search
        return load_file(filename, **kwargs)

labels_pop(labels, index)

Remove and return a labeled frame at the given index.

This provides backward compatibility for the missing pop() method.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
def labels_pop(labels: Labels, index: int) -> LabeledFrame:
    """Remove and return a labeled frame at the given index.

    This provides backward compatibility for the missing pop() method.
    """
    if 0 <= index < len(labels.labeled_frames):
        return labels.labeled_frames.pop(index)
    else:
        raise IndexError(
            f"Index {index} out of range for "
            f"{len(labels.labeled_frames)} labeled frames"
        )

labels_remove_frame(labels, labeled_frame)

Remove a single labeled frame from the Labels object.

This provides backward compatibility for a missing remove_frame() method. Uses the existing remove_frames() function.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1286
1287
1288
1289
1290
1291
1292
def labels_remove_frame(labels: Labels, labeled_frame: LabeledFrame):
    """Remove a single labeled frame from the Labels object.

    This provides backward compatibility for a missing remove_frame() method.
    Uses the existing remove_frames() function.
    """
    remove_frames(labels, [labeled_frame])

make_video_callback(search_paths=None, use_gui=False, context=None)

Adapter function for callback function to finding missing video.

The callback can be used while loading a saved project and allows the user to find videos which have been moved (or have paths from a different system).

The callback function returns True to signal "abort". Args: search_paths: If specified, this is a list of paths where we'll automatically try to find the missing videos. context: A dictionary containing a "changed_on_load" key with a boolean value. Used externally to determine if any filenames were updated. Returns: The callback function.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
def make_video_callback(
    search_paths: Optional[List] = None,
    use_gui: bool = False,
    context: Optional[Dict[str, bool]] = None,
):
    """Adapter function for callback function to finding missing video.

    The callback can be used while loading a saved project and
    allows the user to find videos which have been moved (or have
    paths from a different system).

    The callback function returns True to signal "abort".
    Args:
        search_paths: If specified, this is a list of paths where we'll
            automatically try to find the missing videos.
        context: A dictionary containing a "changed_on_load" key with a boolean
            value. Used externally to determine if any filenames were updated.
    Returns:
        The callback function.

    """
    search_paths = search_paths or []
    context = context or {}

    def video_callback(
        video_list: List[Video],
        new_paths: List[str] = search_paths,
        context: Optional[Dict] = context,
    ):
        """Callback to find videos which have been moved (or moved across systems).
        Args:
            video_list: A list of serialized `Video` objects stored as nested
                dictionaries.
            new_paths: If specified, this is a list of paths where we'll
                automatically try to find the missing videos.
            context: A dictionary containing a "changed_on_load" key with a boolean
                value. Used externally to determine if any filenames were updated.
        """
        filenames = [item.filename for item in video_list]
        context = context or {"changed_on_load": False}

        # Equivalent to pathutils.list_file_missing(filenames)
        missing = [not os.path.exists(filename) for filename in filenames]

        # Try changing the prefix using saved patterns
        if sum(missing):
            fix_paths_with_saved_prefix(filenames, missing)

        # Check for file in search_path dirctories
        if sum(missing) and new_paths:
            for i, filename in enumerate(filenames):
                fixed_path = find_path_using_paths(filename, new_paths)
                if fixed_path != filename:
                    filenames[i] = fixed_path
                    missing[i] = False
                    context["changed_on_load"] = True

        if use_gui:
            # If there are still missing paths, prompt user
            if sum(missing):
                # If we are using dummy for any video not found by user
                # then don't require user to find everything.
                allow_incomplete = USE_DUMMY_FOR_MISSING_VIDEOS

                okay = MissingFilesDialog(
                    filenames, missing, allow_incomplete=allow_incomplete
                ).exec_()

                if not okay:
                    return True  # True for stop

                context["changed_on_load"] = True

        if not use_gui and sum(missing):
            # If we got the same number of paths as there are videos
            if len(filenames) == len(new_paths):
                # and the file extensions match
                exts_match = all(
                    (
                        old.split(".")[-1] == new.split(".")[-1]
                        for old, new in zip(filenames, new_paths)
                    )
                )

                if exts_match:
                    # then the search paths should be a list of all the
                    # video paths, so we can get the new path for the missing
                    # old path.
                    for i, filename in enumerate(filenames):
                        if missing[i]:
                            filenames[i] = new_paths[i]
                            missing[i] = False

                    # Solely for testing since only gui will have a `CommandContext`
                    context["changed_on_load"] = True

        # Replace the video filenames with changes by user
        for i, item in enumerate(video_list):
            item.replace_filename(filenames[i])

        if USE_DUMMY_FOR_MISSING_VIDEOS and sum(missing):
            # Replace any video still missing with "dummy" video
            for is_missing, item in zip(missing, video_list):
                from sleap.io.video import DummyVideo

                vid = DummyVideo(filename=item.filename)
                item["backend"] = cattr.unstructure(vid)

    return video_callback

merge_nodes(base_node, merge_node, labels, skeleton)

Merge two nodes and update data accordingly.

Parameters:

Name Type Description Default
base_node str

Name of skeleton node that will remain after merging.

required
merge_node str

Name of skeleton node that will be merged into the base node.

required
Notes

This method can be used to merge two nodes that might have been named differently but that should be associated with the same node.

This is useful, for example, when merging a different set of labels where a node was named differently. an

If the base_node is visible and has data, it will not be updated. Otherwise, it will be updated with the data from the merge_node on the same instance.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
def merge_nodes(
    base_node: str,
    merge_node: str,
    labels: Optional[Labels],
    skeleton: Optional[Skeleton],
):
    """Merge two nodes and update data accordingly.

    Args:
        base_node: Name of skeleton node that will remain after merging.
        merge_node: Name of skeleton node that will be merged into the base node.

    Notes:
        This method can be used to merge two nodes that might have been named
        differently but that should be associated with the same node.

        This is useful, for example, when merging a different set of labels where
        a node was named differently. an

        If the `base_node` is visible and has data, it will not be updated.
        Otherwise, it will be updated with the data from the `merge_node` on the
        same instance.
    """
    # Labels <- List<LabeledFrame> <- List<Instance> hierarchy
    for inst in instances(labels, skeleton):
        # inst._merge_nodes_data(base_node, merge_node)
        points_array = inst.points
        predicted_inst = inst.from_predicted
        merge_nodes_data(predicted_inst, points_array, base_node, merge_node)

    # Remove merge node from skeleton.
    # skeleton.delete_node(merge_node)
    skeleton.remove_node(merge_node)

merge_nodes_data(predicted_instance, points_array, base_node, merge_node)

Copy point data from one node to another.

Parameters:

Name Type Description Default
base_node str

Name of node that will be merged into.

required
merge_node str

Name of node that will be removed after merge.

required
Notes

This is used when merging skeleton nodes and should not be called directly.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
def merge_nodes_data(
    predicted_instance: PredictedInstance,
    points_array: PointsArray,
    base_node: str,
    merge_node: str,
):
    """Copy point data from one node to another.

    Args:
        base_node: Name of node that will be merged into.
        merge_node: Name of node that will be removed after merge.

    Notes:
        This is used when merging skeleton nodes and should not be called directly.
    """

    base_pt = points_array.__getitem__(base_node) if points_array is not None else None
    merge_pt = (
        points_array.__getitem__(merge_node) if points_array is not None else None
    )

    # check x coordinate not NaN
    if math.isnan(merge_pt["xy"][0]):
        return

    # check y coordinate not NaN
    if math.isnan(merge_pt["xy"][1]) or not base_pt["visible"]:
        base_pt["xy"][0] = merge_pt["xy"][0]
        base_pt["xy"][1] = merge_pt["xy"][1]
        base_pt["visible"] = merge_pt["visible"]
        base_pt["complete"] = merge_pt["complete"]
        # if hasattr(base_instance.from_predicted, 'score'):
        predicted_points_array = predicted_instance.points
        if hasattr(predicted_instance, "score"):
            predicted_points_array.get("base_node")["score"] = (
                predicted_points_array.get("merge_node")["score"]
            )

remove_all_tracks(labels)

Remove all tracks from the labels dataset and update all related instances.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
 95
 96
 97
 98
 99
100
101
def remove_all_tracks(labels: Labels):
    """Remove all tracks from the labels dataset and update all related instances."""
    for lf in labels:
        for instance in lf.instances:
            instance.track = None
    labels.tracks = []
    return labels

remove_frames(labels, frames)

Remove a list of frames from the labels dataset.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
104
105
106
107
108
109
110
111
112
113
def remove_frames(labels: Labels, frames: List[LabeledFrame]):
    """Remove a list of frames from the labels dataset."""
    for lf in frames:
        for lf_idx, lab_fr in enumerate(labels):
            if (
                lab_fr.video.matches_content(lf.video)
                and lab_fr.frame_idx == lf.frame_idx
            ):
                labels_pop(labels, lf_idx)
    labels.update()

remove_instance(labels, instance, lf)

Remove an instance from a labeled frame and update all related instances.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def remove_instance(labels: Labels, instance: Instance, lf: LabeledFrame):
    """Remove an instance from a labeled frame and update all related instances."""
    import numpy as np

    lf_inst_to_remove = labels.find(video=lf.video, frame_idx=lf.frame_idx)[0]
    if lf_inst_to_remove:
        # Iterate backwards to safely remove from list
        for inst_idx in range(len(lf_inst_to_remove.instances) - 1, -1, -1):
            inst = lf_inst_to_remove.instances[inst_idx]

            # Compare instances using numpy arrays with NaN handling
            points_match = np.array_equal(
                inst.numpy(), instance.numpy(), equal_nan=True
            )

            if points_match:
                # Also check track matching
                tracks_match = False
                if inst.track is not None and instance.track is not None:
                    tracks_match = inst.track.matches(instance.track)
                elif inst.track is None and instance.track is None:
                    tracks_match = True

                if tracks_match:
                    lf_inst_to_remove.instances.pop(inst_idx)
                    break  # Only remove first match

remove_track(labels, track)

Remove a track from the labels dataset and update all related instances.

This function removes the specified track from the labels dataset by: 1. Setting the track to None for all instances that were using this track 2. Removing the track from the labels.tracks list

Parameters:

Name Type Description Default
labels Labels

The Labels object containing the dataset to modify

required
track Track

The Track object to remove from the dataset

required

Returns:

Name Type Description
Labels

The modified labels object (same object, modified in-place)

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def remove_track(labels: Labels, track: Track):
    """Remove a track from the labels dataset and update all related instances.

    This function removes the specified track from the labels dataset by:
    1. Setting the track to None for all instances that were using this track
    2. Removing the track from the labels.tracks list

    Args:
        labels: The Labels object containing the dataset to modify
        track: The Track object to remove from the dataset

    Returns:
        Labels: The modified labels object (same object, modified in-place)
    """
    for lf in labels:
        for instance in lf.instances:
            if track.matches(instance.track):
                instance.track = None

    tracks = []
    for t in labels.tracks:
        if not track.matches(t):
            tracks.append(t)

    labels.tracks = tracks
    return labels

remove_unused_tracks(labels)

Remove all tracks from the labels dataset that are not used by any instances.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def remove_unused_tracks(labels: Labels):
    """Remove all tracks from the labels dataset that are not used by any instances."""
    if len(labels.tracks) == 0:
        return

    # Check which tracks are used by instances
    all_tracks = set([track.name for track in labels.tracks])
    used_tracks = set()
    for lf in labels:
        for inst in lf.instances:
            used_tracks.add(inst.track.name)

    # Remove set difference from tracks in Labels
    tracks_to_remove = all_tracks - used_tracks
    for track in tracks_to_remove:
        for t_idx, t in enumerate(labels.tracks):
            if t.name == track:
                labels.tracks.pop(t_idx)

remove_video(labels, video)

Remove a video from the labels dataset and update all related instances.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def remove_video(labels: Labels, video: Video):
    """Remove a video from the labels dataset and update all related instances."""
    # Remove labeled frames for this video (iterate backwards to avoid index issues)
    for lf_idx in reversed(range(len(labels.labeled_frames))):
        lf = labels.labeled_frames[lf_idx]
        if lf.video.matches_content(video):
            labels_pop(labels, lf_idx)

    # Remove video from videos list (iterate backwards to avoid index issues)
    for vid_idx in reversed(range(len(labels.videos))):
        vid = labels.videos[vid_idx]
        if video == vid:
            labels.videos.pop(vid_idx)

        # Remove any suggestions for this video
        if hasattr(labels, "suggestions"):
            labels.suggestions = [
                s for s in labels.suggestions if not s.video.matches_content(video)
            ]

track_set_instance(labels, frame, instance, new_track)

Set track on given instance, updating occupancy.

Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
def track_set_instance(
    labels: Labels, frame: LabeledFrame, instance: Instance, new_track: Track
):
    """Set track on given instance, updating occupancy."""
    track_swap(
        labels,
        frame.video,
        new_track,
        instance.track,
        (frame.frame_idx, frame.frame_idx + 1),
    )
    instance.track = new_track

track_swap(labels, video, new_track, old_track, frame_range)

Swap track assignment for instances in two tracks.

If you need to change the track to or from None, you'll need to use :meth:track_set_instance for each specific instance you want to modify.

Parameters:

Name Type Description Default
video Video

The :class:Video for which we want to swap tracks.

required
new_track Track

A :class:Track for which we want to swap instances with another track.

required
old_track Optional[Track]

The other :class:Track for swapping.

required
frame_range tuple

Tuple of (start, end) frame indexes. If you want to swap tracks on a single frame, use (frame index, frame index + 1).

required
Source code in sleap/sleap_io_adaptors/lf_labels_utils.py
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
def track_swap(
    labels: Labels,
    video: Video,
    new_track: Track,
    old_track: Optional[Track],
    frame_range: tuple,
):
    """Swap track assignment for instances in two tracks.

    If you need to change the track to or from None, you'll need
    to use :meth:`track_set_instance` for each specific
    instance you want to modify.

    Args:
        video: The :class:`Video` for which we want to swap tracks.
        new_track: A :class:`Track` for which we want to swap
            instances with another track.
        old_track: The other :class:`Track` for swapping.
        frame_range: Tuple of (start, end) frame indexes.
            If you want to swap tracks on a single frame, use
            (frame index, frame index + 1).
    """
    # labels._cache.track_swap(video, old_track, new_track, frame_range)

    # Get all instances in old/new tracks
    old_track_instances = find_track_occupancy(labels, video, old_track, frame_range)
    new_track_instances = find_track_occupancy(labels, video, new_track, frame_range)

    # Swap instances between old and new tracks
    for instance in old_track_instances:
        instance.track = new_track
    # old_track can be `Track` or int
    # If int, it's index in instance list which we'll use as a psudo-track,
    # but we won't set instance currently on new_track to old_track
    if type(old_track) == Track:
        # Only clear old track if it's a real track
        for instance in new_track_instances:
            instance.track = old_track