diff --git a/examples/compute_polar_coordinates.py b/examples/compute_polar_coordinates.py index ef0622f4..bad25b79 100644 --- a/examples/compute_polar_coordinates.py +++ b/examples/compute_polar_coordinates.py @@ -15,7 +15,6 @@ from matplotlib import pyplot as plt from movement import sample_data -from movement.analysis.kinematics import compute_head_direction_vector from movement.io import load_poses from movement.utils.vector import cart2pol, pol2cart @@ -49,14 +48,20 @@ # To demonstrate how polar coordinates can be useful in behavioural analyses, # we will compute the head vector of the mouse. # -# In ``movement``, head vector is defined as the vector perpendicular to the -# line connecting two symmetrical keypoints on either side of the head (usually -# the ears), pointing forwards. (See :func:`here\ -# ` for a more -# detailed explanation). +# We define it as the vector from the midpoint between the ears to the snout. -head_vector = compute_head_direction_vector(position, "left_ear", "right_ear") +# compute the midpoint between the ears +midpoint_ears = position.sel(keypoints=["left_ear", "right_ear"]).mean( + dim="keypoints" +) +# compute the head vector +head_vector = position.sel(keypoints="snout") - midpoint_ears + +# drop the keypoints dimension +# (otherwise the `head_vector` data array retains a `snout` keypoint from the +# operation above) +head_vector = head_vector.drop_vars("keypoints") # %% # Visualise the head trajectory @@ -67,12 +72,9 @@ # We can start by plotting the trajectory of the midpoint between the ears. We # will refer to this as the head trajectory. -midpoint_ears = position.sel(keypoints=["left_ear", "right_ear"]).mean( - dim="keypoints" -) +fig, ax = plt.subplots(1, 1) mouse_name = ds.individuals.values[0] -fig, ax = plt.subplots(1, 1) sc = ax.scatter( midpoint_ears.sel(individuals=mouse_name, space="x"), midpoint_ears.sel(individuals=mouse_name, space="y"), diff --git a/movement/analysis/kinematics.py b/movement/analysis/kinematics.py index a2689c0c..b1bce6ac 100644 --- a/movement/analysis/kinematics.py +++ b/movement/analysis/kinematics.py @@ -177,31 +177,13 @@ def compute_forward_vector( right_keypoint: str, camera_view: Literal["top_down", "bottom_up"] = "top_down", ): - """Compute a 2D forward vector given two left-right symmetrical keypoints. + """Compute a 2D forward vector given two left-right symmetric keypoints. The forward vector is computed as a vector perpendicular to the line connecting two symmetrical keypoints on either side of the body (i.e., symmetrical relative to the mid-sagittal plane), and pointing forwards (in the rostral direction). A top-down or bottom-up view of the - animal is assumed. - - To determine the forward direction of the animal, we need to specify - (1) the right-to-left direction of the animal and (2) its upward direction. - We determine the right-to-left direction via the input left and right - keypoints. The upwards direction, in turn, can be determined by passing the - ``camera_view`` argument with either ``"top_down"`` or ``"bottom_up"``. If - the camera view is specified as being ``top_down``, or if no additional - information is provided, we assume that the upwards direction matches that - of the vector [0, 0, -1]. If the camera view is ``bottom_up``, the upwards - direction is assumed to be given by [0, 0, 1]. For both cases, we assume - that position values are expressed in the image coordinate system (where - the positive X-axis is oriented to the right, the positive Y-axis faces - downwards, and positive Z-axis faces away from the person viewing the - screen). - - If one of the required pieces of information is missing for a frame (e.g., - the left keypoint is not visible), then the computed head direction vector - is set to NaN. + animal is assumed (see Notes). Parameters ---------- @@ -227,6 +209,26 @@ def compute_forward_vector( dimensions matching the input data array, but without the ``keypoints`` dimension. + Notes + ----- + To determine the forward direction of the animal, we need to specify + (1) the right-to-left direction of the animal and (2) its upward direction. + We determine the right-to-left direction via the input left and right + keypoints. The upwards direction, in turn, can be determined by passing the + ``camera_view`` argument with either ``"top_down"`` or ``"bottom_up"``. If + the camera view is specified as being ``"top_down"``, or if no additional + information is provided, we assume that the upwards direction matches that + of the vector ``[0, 0, -1]``. If the camera view is ``"bottom_up"``, the + upwards direction is assumed to be given by ``[0, 0, 1]``. For both cases, + we assume that position values are expressed in the image coordinate + system (where the positive X-axis is oriented to the right, the positive + Y-axis faces downwards, and positive Z-axis faces away from the person + viewing the screen). + + If one of the required pieces of information is missing for a frame (e.g., + the left keypoint is not visible), then the computed head direction vector + is set to NaN. + """ # Validate input data _validate_type_data_array(data) @@ -241,7 +243,7 @@ def compute_forward_vector( if len(data.space) != 2: raise log_error( ValueError, - "Input data must have 2 (and only 2) spatial dimensions, but " + "Input data must have exactly 2 spatial dimensions, but " f"currently has {len(data.space)}.", ) @@ -290,7 +292,7 @@ def compute_head_direction_vector( This function is an alias for :func:`compute_forward_vector()\ `. For more detailed information on how the head direction vector is computed, - please refer to the documentation for this function. + please refer to the documentation for that function. Parameters ---------- diff --git a/tests/test_unit/test_kinematics.py b/tests/test_unit/test_kinematics.py index 01bf3ef6..a54d199c 100644 --- a/tests/test_unit/test_kinematics.py +++ b/tests/test_unit/test_kinematics.py @@ -307,7 +307,7 @@ def test_compute_forward_vector(valid_data_array_for_forward_vector): ( "invalid_spatial_dimensions_for_forward_vector", ValueError, - "must have 2 (and only 2) spatial dimensions", + "must have exactly 2 spatial dimensions", ["left_ear", "right_ear"], ), (