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

Moseq Pipeline #1056

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open

Moseq Pipeline #1056

wants to merge 40 commits into from

Conversation

samuelbray32
Copy link
Collaborator

@samuelbray32 samuelbray32 commented Aug 6, 2024

Description

  • Implementation of moseq withing spyglass pipelines.

    • Keypoint data sourced from PositionOutput
      • Added PositionOutput.get_pose_dataframe to allow fetching of full pose rather than centroid for appropriate pipelines.
    • PoseGroup used to combine multiple epochs of pose data into a training set (analagous to PositionGroup in decoding pipeline)
    • Model parameters and training arguments stored in MoseqModelParams
      • Can define initial model as an existing entry in MoseqModel for reuse and extended training
    • MoseqModelSelection combines PoseGroup andmodel parameters
    • MoseqModel trains model and stores in external project file
    • MoseqSyllable (and it's selction table) apply a trained model to an entry from PositionOutput
      • stores syllables and latent variables as a dataframe in an AnalysisNwbfile entry
  • Added utility function get_position_interval_epoch

My To-dos

  • improve the tutorial with more explanations of the steps
  • update notebook image to use PositionOutput
  • change the model stored directories to the spyglass config
  • make a better standard version for getting epoch from the position interval name (as mentioned by Chris before)
  • update changelog

Checklist:

  • N This PR should be accompanied by a release: (yes/no/unsure)
  • NA If release, I have updated the CITATION.cff
  • N This PR makes edits to table definitions: (yes/no)
  • NA If table edits, I have included an alter snippet for release notes.
  • NA If this PR makes changes to position, I ran the relevant tests locally.
  • I have updated the CHANGELOG.md with PR number and description.
  • I have added/edited docs/notebooks to reflect the changes

Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@edeno edeno requested a review from CBroz1 August 8, 2024 18:37
Copy link
Member

@CBroz1 CBroz1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a first pass on the behavior subpackage - Can continue review tomorrow

src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
Copy link
Member

@CBroz1 CBroz1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good so far!

comments=pos_obj.comments,
description=pos_obj.description,
)
if isinstance(pos_nwb["dlc_position"], pd.DataFrame):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would migrate these to a separate method to clean up the indenting, something like declare_pos_analysis_file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CBroz1, reorganized this make function. Let me know if it looks cleaner to you

src/spyglass/position/v1/position_dlc_pose_estimation.py Outdated Show resolved Hide resolved
src/spyglass/position/v1/position_trodes_position.py Outdated Show resolved Hide resolved
@edeno edeno added enhancement New feature or request position labels Sep 25, 2024
@samuelbray32
Copy link
Collaborator Author

samuelbray32 commented Dec 20, 2024

I've now merged in the main branch and have tested it working with a dependency on PositionOutput rather than the introduced PoseOutput table proposed before.

@samuelbray32 samuelbray32 marked this pull request as ready for review December 27, 2024 19:58
@edeno edeno requested review from CBroz1 and edeno December 27, 2024 20:56
Copy link
Collaborator

@edeno edeno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the dependencies to the pyproject.toml and environment.yml?

@samuelbray32
Copy link
Collaborator Author

Can you add the dependencies to the pyproject.toml and environment.yml?

Yeah. Do we want them in the default dependencies or should they be their own version similar to how we do the DLC ones?

Copy link
Member

@CBroz1 CBroz1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did another pass here with some comments. Are pytests in the scope of this PR, or do we want to hold off?

src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/core.py Outdated Show resolved Hide resolved
src/spyglass/behavior/moseq.py Outdated Show resolved Hide resolved
# increment param name
if new_name is None:
# increment the extension number
if model_key["model_params_name"][:-1].endswith("_extension"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be avoided by adding a pk field for extension that's an int with a default value of 0?

@@ -686,6 +686,30 @@ def get_interval_list_name_from_epoch(nwb_file_name: str, epoch: int) -> str:
return interval_names[0]


def get_position_interval_epoch(
nwb_file_name: str, position_interval_name: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nwb_file_name: str, position_interval_name: str
nwb_file_name: str, position_interval_name: str, limit=1

Comment on lines +707 to +710
query = PositionIntervalMap * TaskEpoch & key
if query:
return query.fetch1("epoch")
return None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows you to try N times, whatever you set the default value of limit to

Suggested change
query = PositionIntervalMap * TaskEpoch & key
if query:
return query.fetch1("epoch")
return None
return get_position_interval_epoch(limit=limit-1) if limit else None

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need to try more than twice. The second attempt is just after ensuring that all epoch intervals have been mapped to a position interval. That wouldn't change with further attempts

@@ -409,6 +409,16 @@ def fetch_dataframe(self, *attrs, **kwargs) -> pd.DataFrame:
axis=1,
)

def fetch_video_path(self):
"""Return the video path for pose estimate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, we pass key as an arg for this kind of thing. It's not clear to me why you break the pattern here. If no arg, it could be a property, yeah? If kept, I'd add example use to the docstring

Suggested change
"""Return the video path for pose estimate
"""Return the video path for pose estimate
>>> (DLCPoseEstimation & key).fetch_video_path()


def fetch_video_path(self):
"""Return the video path for pose estimate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above note on break from key-as-arg convention

@@ -269,6 +294,27 @@ def evaluate_pose_estimation(cls, key):
}
return sub_thresh_percent_dict

def fetch_pose_dataframe(self):
"""fetches the pose data from the pose estimation table

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above note on break from key-as-arg convention

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one I think is reasonable as-is. It matches the convention we use for fetch1_dataframe or fetch_nwb

@CBroz1
Copy link
Member

CBroz1 commented Jan 2, 2025

Yeah. Do we want them in the default dependencies or should they be their own version similar to how we do the DLC ones?

A user will need a DLC project in place to use MoSeq, right? I think I might rename our 'dlc' optional dependency to 'position' and moseq dependencies in there - not sure which I prefer across the following:

pip install .[dlc,moseq] # separate categories in pyproject
pip install .[position] 
pip install .[pos_deps]

@samuelbray32
Copy link
Collaborator Author

Yeah. Do we want them in the default dependencies or should they be their own version similar to how we do the DLC ones?

A user will need a DLC project in place to use MoSeq, right? I think I might rename our 'dlc' optional dependency to 'position' and moseq dependencies in there - not sure which I prefer across the following:

pip install .[dlc,moseq] # separate categories in pyproject
pip install .[position] 
pip install .[pos_deps]

I may lean towards keeping them separate. You don't strictly need DLC in your environment to run the moseq pipeline.
Examples:

  • User 2 is pulling pose data from a pre-existing DLC entry made by a different User 1
  • I think it would be reasonable future enhancement to have some version of a ImportedPose table to allow new users to bring pre-existing DLC annotated data into spyglass (analogous to ImportedSpikeSorting)

@samuelbray32 samuelbray32 requested a review from edeno January 16, 2025 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request position
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants