Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to import skeletons #93

Open
lochhh opened this issue Dec 4, 2023 · 6 comments
Open

Add the ability to import skeletons #93

lochhh opened this issue Dec 4, 2023 · 6 comments

Comments

@lochhh
Copy link
Collaborator

lochhh commented Dec 4, 2023

For SLEAP we could have something like load_skeleton.from_sleap_json()

Originally posted by @niksirbi in #88 (comment)

@niksirbi
Copy link
Member

Skan is a pure Python library for skeleton analysis, may be relevant/useful for us some day. Shout out to @vigji for bringing it to my attention.

They even have a guide on displaying 3D skeletons in napari.

@vigji
Copy link
Collaborator

vigji commented Dec 6, 2024

Out of curiosity, how come you don't consider anywhere in the current implementation of the movement data the skeleton?

For me it is important enough to deserve a dedicated entry in the pose xarray, what do you think? I could not track down a discussion on this, sorry if I missed it.

@niksirbi
Copy link
Member

niksirbi commented Dec 9, 2024

I agree that skeletons are important; we had just neglected them when we first designed the data structure.

I guess there are two questions to resolve:

Skeleton Representation

  • A minimal approach would be to have a dictionary with the reserved name "skeleton" added to the dataset's attributes. The dictionary can map nodes to other nodes, i.e., represent the edges.
  • Another option is to dedicate a special DataArray to the skeleton. This would be a boolean array of shape (n_keypoints, n_keypoints), with True where there is an edge (though this adds some redundancy, as the matrix would be symmetric).
  • A completely separate object corresponding to a graph, for example adopting the Skeleton representations of sleap-io or Skan. A "hybrid" approach could be to serialise this object to the attrs dictionary attached to the xarray Dataset.

Were you thinking of one of the above, or something else?

In any case, the skeleton should be optional, in my opinion, because:

  • They are not necessary for many analyses.
  • They cannot be defined for some tracking modalities (centroid, bbox, or segmentation mask tracking).

Loading Skeletons

I haven't given much thought to this, but I'll share some ideas off the top of my head.

  • Add an argument to existing loader functions, for example:
    load_poses.from_dlc_file(file_path: str | Path, fps: float | None, skeleton_file: str | Path | None)
    Here, the skeleton would be parsed from a file and attached to the poses dataset in one step.
  • Create completely separate functions for loading or creating skeletons.
    skeleton = load_skeleton(file_path: str | Path, format: str = "sleap_json")
    skeleton = create_skeleton(skeleton_dict: dict, name: str)
    Here, "attaching" the skeleton to a poses dataset would be handled separately from its loading or creation.

Ultimately, "how to load skeletons" will depend on how we choose to represent them, so the two questions are interconnected.

@vigji
Copy link
Collaborator

vigji commented Dec 9, 2024

I think 0 sofistication is required here, and the minimal representation of list of edges is by far the most convenient one. Would not add the burden of dealing with a separate library dedicated to them, as I can't see the need for any fancy topological optimisation on skeleton operations, and everyone understands easily a list of nodes.

Fully agree on not having it mandatory; I would indeed just add the option to have it passed as an argument, either row (list of lists) or via a config file (eg for DLC) from which it could be read. slp files should already contain it

What do you think?

@niksirbi
Copy link
Member

niksirbi commented Dec 9, 2024

I'm with you on keeping it simple.
I like the list-of-edges representation, as this is exactly how most users define skeletons in DLC or SLEAP.

So an example skeleton could be:

skeleton = [
	("snout", "left_ear"),
	("snout", "right_ear"),
    ("snout", "neck"),
    ("neck", "centroid"),
    ("centroid", "tail_base"),
    ("tail_base", "tail_end"),
]

The above shows list-of-tuples, but list-of-lists should also be acceptable.

We could have a function named add_skeleton(ds: xarray.Dataset, edges: list[tuple] | list[list]). That function would validate the edges (as in, ensure the nodes really exist as keypoints in ds), and then call ds.attrs["skeleton"] = validated_edges.

The user could directly overwrite that attribute at any point, at their own risk.

In load_poses.from_file() functions, I'm thinking of adding a "skeleton_filepath" argument pointing to where the skeleton is stored, which is usually not the same as the file where predicted poses are stored (for DLC it's the config file, for sleap the skeleton can be separately saved as a .json, or read directly from the .slp).

@sfmig
Copy link
Contributor

sfmig commented Dec 13, 2024

We think the main use cases for the skeleton data would be:

  • visualisation
  • compute length of segments
  • compute angles between segments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🤔 Triage
Development

No branches or pull requests

4 participants