Source code for sleap.io.format.leap_matlab
"""
Adaptor to read (not write) LEAP MATLAB data files.
This attempts to find videos. If they cannot automatically be found and the
`gui` param is True, then the user will be prompted to find the videos.
"""
import os
import scipy.io as sio
from sleap import Labels, Video, Skeleton
from sleap.gui.dialogs.missingfiles import MissingFilesDialog
from sleap.instance import (
Instance,
LabeledFrame,
Point,
)
from .adaptor import Adaptor, SleapObjectType
from .filehandle import FileHandle
[docs]class LabelsLeapMatlabAdaptor(Adaptor):
@property
def handles(self):
return SleapObjectType.labels
@property
def default_ext(self):
return "mat"
@property
def all_exts(self):
return ["mat"]
@property
def name(self):
return "LEAP Matlab dataset"
[docs] def can_read_file(self, file: FileHandle):
if not self.does_match_ext(file.filename):
return False
# if "boxPath" not in file.file:
# return False
return True
[docs] def can_write_filename(self, filename: str):
return self.does_match_ext(filename)
[docs] def does_read(self) -> bool:
return True
[docs] def does_write(self) -> bool:
return False
[docs] @classmethod
def read(
cls, file: FileHandle, gui: bool = True, *args, **kwargs,
):
filename = file.filename
mat_contents = sio.loadmat(filename)
box_path = cls._unwrap_mat_scalar(mat_contents["boxPath"])
# If the video file isn't found, try in the same dir as the mat file
if not os.path.exists(box_path):
file_dir = os.path.dirname(filename)
box_path_name = box_path.split("\\")[-1] # assume windows path
box_path = os.path.join(file_dir, box_path_name)
if not os.path.exists(box_path):
if gui:
video_paths = [box_path]
missing = [True]
okay = MissingFilesDialog(video_paths, missing).exec_()
if not okay or missing[0]:
return
box_path = video_paths[0]
else:
# Ignore missing videos if not loading from gui
box_path = ""
if os.path.exists(box_path):
vid = Video.from_hdf5(
dataset="box", filename=box_path, input_format="channels_first"
)
else:
vid = None
nodes_ = mat_contents["skeleton"]["nodes"]
edges_ = mat_contents["skeleton"]["edges"]
points_ = mat_contents["positions"]
edges_ = edges_ - 1 # convert matlab 1-indexing to python 0-indexing
nodes = cls._unwrap_mat_array(nodes_)
edges = cls._unwrap_mat_array(edges_)
nodes = list(map(str, nodes)) # convert np._str to str
sk = Skeleton(name=filename)
sk.add_nodes(nodes)
for edge in edges:
sk.add_edge(source=nodes[edge[0]], destination=nodes[edge[1]])
labeled_frames = []
node_count, _, frame_count = points_.shape
for i in range(frame_count):
new_inst = Instance(skeleton=sk)
for node_idx, node in enumerate(nodes):
x = points_[node_idx][0][i]
y = points_[node_idx][1][i]
new_inst[node] = Point(x, y)
if len(new_inst.points):
new_frame = LabeledFrame(video=vid, frame_idx=i)
new_frame.instances = (new_inst,)
labeled_frames.append(new_frame)
labels = Labels(labeled_frames=labeled_frames, videos=[vid], skeletons=[sk])
return labels
@classmethod
def _unwrap_mat_scalar(cls, a):
"""Extract single value from nested MATLAB file data."""
if a.shape == (1,):
return cls._unwrap_mat_scalar(a[0])
else:
return a
@classmethod
def _unwrap_mat_array(cls, a):
"""Extract list of values from nested MATLAB file data."""
b = a[0][0]
c = [cls._unwrap_mat_scalar(x) for x in b]
return c