-
Notifications
You must be signed in to change notification settings - Fork 8
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
Compute locomotion features #106
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #106 +/- ##
==========================================
+ Coverage 99.13% 99.17% +0.04%
==========================================
Files 8 9 +1
Lines 461 487 +26
==========================================
+ Hits 457 483 +26
Misses 4 4 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @lochhh thanks for this!
I have some high-level questions/comments regarding this implementation.
User interface
With the current implementation, the use-case would be something like:
from movement import sample_data
from movement.analysis import kinematics
ds = sample_data.fetch_sample_data(
"SLEAP_three-mice_Aeon_proofread.predictions.slp"
)
ds["displacement_norm"] = kinematics.displacement_vector(ds["pose_tracks"])["magnitude"]
ds["velocity"] = kinematics.velocity(ds["pose_tracks"])
ds["acceleration"] = kinematics.acceleration(ds["pose_tracks"])
ds["speed"] = kinematics.velocity_vector(ds["pose_tracks"])["magnitude"]
I've played around a bit with using the new module in a notebook, in a separate branch).
This is fine for now, but are we planning to provide a more convenient API for the users later? Perhaps by adding convenience methods/properties to that accessor object? Have I understood correctly that we are leaving that for a later PR?
Regarding the interface as is, there is a bit of repetition because each function appears "twice", once in xy form and once in magnitude/angle form. What do you think about merging the two into something like below?
def velocity(data: xr.DataArray, format: Literal['xy', 'vector']):
There is a bit of an inconsistency I guess, because the xy functions return a DataArray, while the vector methods return a xr.Dataset. Perhaps this will become less consequential if we provide sth like kinematics.compute_all()
which will return an xr.Dataset containing all relevant variables as DataArrays.
If we don't end up merging the two formats as one function, we may have to rename them as velocity_xy
and velocity_vector
, just to be very explicit and to avoid confusion.
A different approach would be to have say one velocity()
function returning 3 DataArrays in the same Dataset: xy, magnitude, angle.
I'm thinking out loud here, none of these opinions are held strongly, so I'll open the discussion to @sfmig and @b-peri as well. Feel free to chime in on this one (no rush).
Testing
Currently, you test by generating some toy pose data, where the kinematic variables are known or very easy to derive, and assert that our functions give the expected results. To be on the extra safe side, shouldn't we also test with some more "real-world" data - i.e. data that contains NaN values perhaps and doesn't have acceleration=0? I wouldn't run the tests on all our sample datasets (that seems excessive and inefficient), but maybe a few of them?
Looking good! ✨ I have two comments but with a caveat: I am not super familiar with the data structure so feel free to take / leave whatever makes most / least sense with the agreed structure. This could also very well be a separate PRs.
Happy to discuss in a behaviour meeting or do some pair programming on this! |
Thanks Sofia! I would also find this quite intuitive, and I like the idea of making more generic methods for operating on vectors, as I foresee much reuse for those. We would have to see how to implement these in the context of One thing that this discussion highlights for me is the importance of defining clear and consistent terminology for the various coordinate systems. We should discuss this in our meeting + hackathon this week. |
Thanks @niksirbi and @sfmig for the great suggestions and feedback! I will incorporate the following small changes for now:
The idea is to eventually have users call @property
def displacement(self) -> xr.DataArray:
"""Return the displacement between consecutive x, y
locations of each keypoint of each individual.
"""
pose_tracks = self._obj[self.var_names[0]]
self._obj["displacement"] = kinematics.compute_displacement(pose_tracks)
return self._obj["displacement"] that allows a user to call The vectors require further discussion this week. I like the idea of having more generic methods, e.g. |
That all sounds very reasonable to me, but one note here: since we will be able access many properties through that object, I wonder whether This goes alongside my other idea of renaming |
Conclusions from chat w/ @niksirbi and @lochhh Agreed terminology
In this PR:
Note that:
|
82b06d9
to
229be0d
Compare
I'm leaning towards adding this to the PR for #118, and then rebase the current PR on top. What are your thoughts? |
I think that makes sense, it better fits the scope of #118 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done @lochhh!
I think this is very close to being merged. Apart from trivial comments, I have two more substantial points to raise:
- Now that the accessor has some useful functionality, it should be added to the API docs (in
api_index.rst
). The class docstring should probably be modiefied to reflect that "pose_tracks" and "confidence" are not the only possible data variables. We should mention that if one of the properties is called, a data variable with the same name will also be created. - the way these properties are currently implemented, they will be computed the first time a user asks for them, and subsequent calls will skip the computation and not overwrite the property. I am wondering about the use-case where a user computes say velocity, then does some additional filtering of pose tracks (perhaps dropping more outliers) and then asks for velocity again. I'm not sure how to deal with that case. But we can discuss tomorrow altogether, since it's also relevant for PR Implement functions to drop and interpolate over low-confidence datapoints #116
b71499f
to
9c92ae8
Compare
Quality Gate passedIssues Measures |
Thanks @niksirbi for reviewing this! I've incorporated the changes you suggested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM now 🚀
* Draft compute velocity * Add test for displacement * Fix confidence values in `valid_pose_dataset` fixture * Refactor kinematics test and functions * Vectorise kinematic functions * Refactor repeated calls to compute magnitude + direction * Displacement to return 0 instead of NaN * Return x y components in kinematic functions * Refactor kinematics tests * Remove unnecessary instantiations * Improve time diff calculation * Prefix kinematics methods with `compute_` * Add kinematic properties to `PosesAccessor` * Update `test_property` docstring * Refactor `_vector` methods and kinematics tests * Update `expected_dataset` docstring * Rename `poses` to `move` in `PosesAccessor` * Refactor properties in `PosesAccessor` * Remove vector util functions and tests * Update `not_a_dataset` fixture description * Validate dataset upon accessor property access * Update `poses_accessor` test description * Validate input data in kinematic functions * Remove unused fixture * Parametrise kinematics tests * Set `compute_derivative` as internal function * Update `kinematics.py` docstrings * Add new modules to API docs * Update `move_accessor` docstrings * Rename `test_move_accessor` filename
* Added `filtering.py` module, w/ draft `interp_pose()` & `filter_confidence()` fxns * Fix logging for operations in place * Renamed fxns to `interpolate_over_time()` and `filter_by_confidence` * Cleaned up code + corrected docstrings * Refactored `filtering.py` to movement base folder * Improved logging logic, fixed diagnostic report, removed in-place * Removed printing of diagnostic report * Updated dependency from `xarray` to `xarray[accel]` * Added testing for `filtering.py` * Minor fixes and clean-up * Reorganise Accessor (#122) * Check for expected `dims` and `data_vars` in dataset * Fix `missing_dim_dataset` fixture * Rename `poses` accessor to `move` * Rename `PoseAccessor` class to `MoveAccessor` * Rename `poses_accessor.py` to `move_accessor.py` * Move `move_accessor.py` to the top level * Fix accessor docstring formatting * Compute locomotion features (#106) * Draft compute velocity * Add test for displacement * Fix confidence values in `valid_pose_dataset` fixture * Refactor kinematics test and functions * Vectorise kinematic functions * Refactor repeated calls to compute magnitude + direction * Displacement to return 0 instead of NaN * Return x y components in kinematic functions * Refactor kinematics tests * Remove unnecessary instantiations * Improve time diff calculation * Prefix kinematics methods with `compute_` * Add kinematic properties to `PosesAccessor` * Update `test_property` docstring * Refactor `_vector` methods and kinematics tests * Update `expected_dataset` docstring * Rename `poses` to `move` in `PosesAccessor` * Refactor properties in `PosesAccessor` * Remove vector util functions and tests * Update `not_a_dataset` fixture description * Validate dataset upon accessor property access * Update `poses_accessor` test description * Validate input data in kinematic functions * Remove unused fixture * Parametrise kinematics tests * Set `compute_derivative` as internal function * Update `kinematics.py` docstrings * Add new modules to API docs * Update `move_accessor` docstrings * Rename `test_move_accessor` filename * Include M1 runners in CI and update install instructions (#125) * also test on macOS 14 M1 runner * conda install hdf5 * Add dependabot config (#128) * Bump actions/cache from 3 to 4 (#130) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](actions/cache@v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [pre-commit.ci] pre-commit autoupdate (#131) updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.3.0](astral-sh/ruff-pre-commit@v0.2.0...v0.3.0) - [github.com/psf/black: 24.1.1 → 24.2.0](psf/black@24.1.1...24.2.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Add codecov token to test_and_deploy.yml (#129) * Add codecov token to test_and_deploy.yml * use test action from main branch * switch back to using v2 of the test action * tweaked phrasing in docstrings * added filtering functions to API index * add note about default confidence threshold * rename and reorganise filter_diagnostics as report_nan_values * use xarray's copy method * max_gaps is in seconds and also works at edges * use xarray's built-in isnull method * added sphinx-gallery example for filtering and interpolation --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Chang Huan Lo <[email protected]> Co-authored-by: Niko Sirmpilatze <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Description
What is this PR
Why is this PR needed?
This PR resolves #3.
What does this PR do?
analysis.kinematics
module with basic functions to computexarray.DataArray
s:PosesAccessor
, allowing the corresponding kinematic properties to be computed and stored in the dataset (if not already). This means that callingds.poses.<kinematic property>
(e.g.ds.poses.velocity
) will return thevelocity
DataArray and store this inds
. Sincevelocity
is now stored inds
, one can retrieve this usingds.velocity
.xarray.Dataset.data_vars
anddims
inPosesAccessor.validate()
References
#3
How has this PR been tested?
New tests have been written and run locally and through CI.
Does this PR require an update to the documentation?
We may want to add this to the "getting started" guide or add to "examples".
Checklist: