Repository: neuroinformatics-unit/movement

Issue:
Compute pairwise distances
A few metrics to consider
- distances between 2 keypoints (within each individual, e.g. `dist_snout_rightear` for each individual)
- distances between 2 individuals (across all pairs of the same keypoints, e.g. `dist_individual1_individual2` contains snout-to-snout, rightear-to-rightear, tail-to-tail, etc. distances between `individual1` and `individual2`)
- distances between different keypoints of different individuals (`dist_individual1snout_individual2rear` contains the distances between `individual1`'s `snout` and `individual2`'s `rear`)

SciPy has [`pdist`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html), which returns a condensed distance matrix. 

See also relevant [SO question](https://stackoverflow.com/questions/53146579/pairwise-distance-between-objects-xarray) on achieving `pdist` in `xarray`

Discussion:
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/neuroinformatics-unit/movement/pull/278?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=neuroinformatics-unit) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.77%. Comparing base [(`abeaff1`)](https://app.codecov.io/gh/neuroinformatics-unit/movement/commit/abeaff10c1742d2f15d97d3668b566313b6ce4b7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=neuroinformatics-unit) to head [(`77e5cf7`)](https://app.codecov.io/gh/neuroinformatics-unit/movement/commit/77e5cf7d53e209a6cc2074570ad0730f26a2e02d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=neuroinformatics-unit).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #278   +/-   ##
=======================================
  Coverage   99.77%   99.77%           
=======================================
  Files          14       14           
  Lines         883      896   +13     
=======================================
+ Hits          881      894   +13     
  Misses          2        2           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/neuroinformatics-unit/movement/pull/278?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=neuroinformatics-unit).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=neuroinformatics-unit).

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


lochhh: Thanks again @niksirbi for the usual thorough review! I've adopted your suggestion to remove the `interkeypoint/individual` wrappers, exposing only `compute_pairwise_distances` , whilst hiding `_cdist` - this makes sense as it's unlikely one would want to compute distances using 2 single-keypoint/individual data arrays from different datasets (with `_cdist`).  Other changes include:
- requiring `pairs` as argument in `compute_pairwise_distances` and allowing the special keyword `'all'`
- renaming `core_dims` &rarr; `labels_dims` to (hopefully) make it clearer that this is the dim to get labels from
- removing the full list of distance metrics, providing only a couple of examples in `_cdist` and `compute_pairwise_distances` docstrings
- raise ValueError when no pairs are found
- remove circularity while generating expected pairs in tests
niksirbi: Thanks @lochhh, I will take another look at this next week, with a fresh mind.
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=neuroinformatics-unit_movement&pullRequest=278&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=neuroinformatics-unit_movement&pullRequest=278&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=neuroinformatics-unit_movement&pullRequest=278)


sfmig: sorry both for the delay and thanks for pinging me ❤️ 

I can have a look on Friday, but I know that would delay this work further, so feel free to go ahead! Maybe I can draft an example instead?
lochhh: > sorry both for the delay and thanks for pinging me ❤️
> 
> I can have a look on Friday, but I know that would delay this work further, so feel free to go ahead! Maybe I can draft an example instead?

That would be perfect! 

Patch:
diff --git a/movement/kinematics.py b/movement/kinematics.py
index 17126d3..a6e0b0b 100644
--- a/movement/kinematics.py
+++ b/movement/kinematics.py
@@ -1,9 +1,11 @@
 """Compute kinematic variables like velocity and acceleration."""
 
+import itertools
 from typing import Literal
 
 import numpy as np
 import xarray as xr
+from scipy.spatial.distance import cdist
 
 from movement.utils.logging import log_error
 from movement.utils.vector import compute_norm
@@ -324,6 +326,336 @@ def compute_head_direction_vector(
     )
 
 
+def _cdist(
+    a: xr.DataArray,
+    b: xr.DataArray,
+    dim: Literal["individuals", "keypoints"],
+    metric: str | None = "euclidean",
+    **kwargs,
+) -> xr.DataArray:
+    """Compute distances between two position arrays across a given dimension.
+
+    This function is a wrapper around :func:`scipy.spatial.distance.cdist`
+    and computes the pairwise distances between the two input position arrays
+    across the dimension specified by ``dim``.
+    The dimension can be either ``individuals`` or ``keypoints``.
+    The distances are computed using the specified ``metric``.
+
+    Parameters
+    ----------
+    a : xarray.DataArray
+        The first input data containing position information of a
+        single individual or keypoint, with ``time``, ``space``
+        (in Cartesian coordinates), and ``individuals`` or ``keypoints``
+        (as specified by ``dim``) as required dimensions.
+    b : xarray.DataArray
+        The second input data containing position information of a
+        single individual or keypoint, with ``time``, ``space``
+        (in Cartesian coordinates), and ``individuals`` or ``keypoints``
+        (as specified by ``dim``) as required dimensions.
+    dim : str
+        The dimension to compute the distances for. Must be either
+        ``'individuals'`` or ``'keypoints'``.
+    metric : str, optional
+        The distance metric to use. Must be one of the options supported
+        by :func:`scipy.spatial.distance.cdist`, e.g. ``'cityblock'``,
+        ``'euclidean'``, etc.
+        Defaults to ``'euclidean'``.
+    **kwargs : dict
+        Additional keyword arguments to pass to
+        :func:`scipy.spatial.distance.cdist`.
+
+    Returns
+    -------
+    xarray.DataArray
+        An xarray DataArray containing the computed distances between
+        each pair of inputs.
+
+    Examples
+    --------
+    Compute the Euclidean distance (default) between ``ind1`` and
+    ``ind2`` (i.e. interindividual distance for all keypoints)
+    using the ``position`` data variable in the Dataset ``ds``:
+
+    >>> pos1 = ds.position.sel(individuals="ind1")
+    >>> pos2 = ds.position.sel(individuals="ind2")
+    >>> ind_dists = _cdist(pos1, pos2, dim="individuals")
+
+    Compute the Euclidean distance (default) between ``key1`` and
+    ``key2`` (i.e. interkeypoint distance for all individuals)
+    using the ``position`` data variable in the Dataset ``ds``:
+
+    >>> pos1 = ds.position.sel(keypoints="key1")
+    >>> pos2 = ds.position.sel(keypoints="key2")
+    >>> key_dists = _cdist(pos1, pos2, dim="keypoints")
+
+    See Also
+    --------
+    scipy.spatial.distance.cdist : The underlying function used.
+    compute_pairwise_distances : Compute pairwise distances between
+        ``individuals`` or ``keypoints``
+
+    """
+    # The dimension from which ``dim`` labels are obtained
+    labels_dim = "individuals" if dim == "keypoints" else "keypoints"
+    elem1 = getattr(a, dim).item()
+    elem2 = getattr(b, dim).item()
+    a = _validate_labels_dimension(a, labels_dim)
+    b = _validate_labels_dimension(b, labels_dim)
+    result = xr.apply_ufunc(
+        cdist,
+        a,
+        b,
+        kwargs={"metric": metric, **kwargs},
+        input_core_dims=[[labels_dim, "space"], [labels_dim, "space"]],
+        output_core_dims=[[elem1, elem2]],
+        vectorize=True,
+    )
+    result = result.assign_coords(
+        {
+            elem1: getattr(a, labels_dim).values,
+            elem2: getattr(a, labels_dim).values,
+        }
+    )
+    # Drop any squeezed coordinates
+    return result.squeeze(drop=True)
+
+
+def compute_pairwise_distances(
+    data: xr.DataArray,
+    dim: Literal["individuals", "keypoints"],
+    pairs: dict[str, str | list[str]] | Literal["all"],
+    metric: str | None = "euclidean",
+    **kwargs,
+) -> xr.DataArray | dict[str, xr.DataArray]:
+    """Compute pairwise distances between ``individuals`` or ``keypoints``.
+
+    This function computes the distances between
+    pairs of ``individuals`` (i.e. interindividual distances) or
+    pairs of ``keypoints`` (i.e. interkeypoint distances),
+    as determined by ``dim``.
+    The distances are computed for the given ``pairs``
+    using the specified ``metric``.
+
+    Parameters
+    ----------
+    data : xarray.DataArray
+        The input data containing position information, with ``time``,
+        ``space`` (in Cartesian coordinates), and
+        ``individuals`` or ``keypoints`` (as specified by ``dim``)
+        as required dimensions.
+    dim : Literal["individuals", "keypoints"]
+        The dimension to compute the distances for. Must be either
+        ``'individuals'`` or ``'keypoints'``.
+    pairs : dict[str, str | list[str]] or 'all'
+        Specifies the pairs of elements (either individuals or keypoints)
+        for which to compute distances, depending on the value of ``dim``.
+
+        - If ``dim='individuals'``, ``pairs`` should be a dictionary where
+          each key is an individual name, and each value is also an individual
+          name or a list of such names to compute distances with.
+        - If ``dim='keypoints'``, ``pairs`` should be a dictionary where each
+          key is a keypoint name, and each value is also keypoint name or a
+          list of such names to compute distances with.
+        - Alternatively, use the special keyword ``'all'`` to compute distances
+          for all possible pairs of individuals or keypoints
+          (depending on ``dim``).
+    metric : str, optional
+        The distance metric to use. Must be one of the options supported
+        by :func:`scipy.spatial.distance.cdist`, e.g. ``'cityblock'``,
+        ``'euclidean'``, etc.
+        Defaults to ``'euclidean'``.
+    **kwargs : dict
+        Additional keyword arguments to pass to
+        :func:`scipy.spatial.distance.cdist`.
+
+    Returns
+    -------
+    xarray.DataArray or dict[str, xarray.DataArray]
+        The computed pairwise distances. If a single pair is specified in
+        ``pairs``, returns an :class:`xarray.DataArray`. If multiple pairs
+        are specified, returns a dictionary where each key is a string
+        representing the pair  (e.g., ``'dist_ind1_ind2'`` or
+        ``'dist_key1_key2'``) and each value is an :class:`xarray.DataArray`
+        containing the computed distances for that pair.
+
+    Raises
+    ------
+    ValueError
+        If ``dim`` is not one of ``'individuals'`` or ``'keypoints'``;
+        if ``pairs`` is not a dictionary or ``'all'``; or
+        if there are no pairs in ``data`` to compute distances for.
+
+    Examples
+    --------
+    Compute the Euclidean distance (default) between ``ind1`` and ``ind2``
+    (i.e. interindividual distance), for all possible pairs of keypoints.
+
+    >>> position = xr.DataArray(
+    ...     np.arange(36).reshape(2, 3, 3, 2),
+    ...     coords={
+    ...         "time": np.arange(2),
+    ...         "individuals": ["ind1", "ind2", "ind3"],
+    ...         "keypoints": ["key1", "key2", "key3"],
+    ...         "space": ["x", "y"],
+    ...     },
+    ...     dims=["time", "individuals", "keypoints", "space"],
+    ... )
+    >>> dist_ind1_ind2 = compute_pairwise_distances(
+    ...     position, "individuals", {"ind1": "ind2"}
+    ... )
+    >>> dist_ind1_ind2
+    <xarray.DataArray (time: 2, ind1: 3, ind2: 3)> Size: 144B
+    8.485 11.31 14.14 5.657 8.485 11.31 ... 5.657 8.485 11.31 2.828 5.657 8.485
+    Coordinates:
+    * time     (time) int64 16B 0 1
+    * ind1     (ind1) <U4 48B 'key1' 'key2' 'key3'
+    * ind2     (ind2) <U4 48B 'key1' 'key2' 'key3'
+
+    The resulting ``dist_ind1_ind2`` is a DataArray containing the computed
+    distances between ``ind1`` and ``ind2`` for all keypoints
+    at each time point.
+
+    To obtain the distances between ``key1`` of ``ind1`` and
+    ``key2`` of ``ind2``:
+
+    >>> dist_ind1_ind2.sel(ind1="key1", ind2="key2")
+
+    Compute the Euclidean distance (default) between ``key1`` and ``key2``
+    (i.e. interkeypoint distance), for all possible pairs of individuals.
+
+    >>> dist_key1_key2 = compute_pairwise_distances(
+    ...     position, "keypoints", {"key1": "key2"}
+    ... )
+    >>> dist_key1_key2
+    <xarray.DataArray (time: 2, key1: 3, key2: 3)> Size: 144B
+    2.828 11.31 19.8 5.657 2.828 11.31 14.14 ... 2.828 11.31 14.14 5.657 2.828
+    Coordinates:
+    * time     (time) int64 16B 0 1
+    * key1     (key1) <U4 48B 'ind1' 'ind2' 'ind3'
+    * key2     (key2) <U4 48B 'ind1' 'ind2' 'ind3'
+
+    The resulting ``dist_key1_key2`` is a DataArray containing the computed
+    distances between ``key1`` and ``key2`` for all individuals
+    at each time point.
+
+    To obtain the distances between ``key1`` and ``key2`` within ``ind1``:
+
+    >>> dist_key1_key2.sel(key1="ind1", key2="ind1")
+
+    To obtain the distances between ``key1`` of ``ind1`` and
+    ``key2`` of ``ind2``:
+
+    >>> dist_key1_key2.sel(key1="ind1", key2="ind2")
+
+    Compute the city block or Manhattan distance for multiple pairs of
+    keypoints using ``position``:
+
+    >>> key_dists = compute_pairwise_distances(
+    ...     position,
+    ...     "keypoints",
+    ...     {"key1": "key2", "key3": ["key1", "key2"]},
+    ...     metric="cityblock",
+    ... )
+    >>> key_dists.keys()
+    dict_keys(['dist_key1_key2', 'dist_key3_key1', 'dist_key3_key2'])
+
+    As multiple pairs of keypoints are specified,
+    the resulting ``key_dists`` is a dictionary containing the DataArrays
+    of computed distances for each pair of keypoints.
+
+    Compute the city block or Manhattan distance for all possible pairs of
+    individuals using ``position``:
+
+    >>> ind_dists = compute_pairwise_distances(
+    ...     position,
+    ...     "individuals",
+    ...     "all",
+    ...     metric="cityblock",
+    ... )
+    >>> ind_dists.keys()
+    dict_keys(['dist_ind1_ind2', 'dist_ind1_ind3', 'dist_ind2_ind3'])
+
+    See Also
+    --------
+    scipy.spatial.distance.cdist : The underlying function used.
+
+    """
+    if dim not in ["individuals", "keypoints"]:
+        raise log_error(
+            ValueError,
+            "'dim' must be either 'individuals' or 'keypoints', "
+            f"but got {dim}.",
+        )
+    if isinstance(pairs, str) and pairs != "all":
+        raise log_error(
+            ValueError,
+            f"'pairs' must be a dictionary or 'all', but got {pairs}.",
+        )
+    validate_dims_coords(data, {"time": [], "space": ["x", "y"], dim: []})
+    # Find all possible pair combinations if 'all' is specified
+    if pairs == "all":
+        paired_elements = list(
+            itertools.combinations(getattr(data, dim).values, 2)
+        )
+    else:
+        paired_elements = [
+            (elem1, elem2)
+            for elem1, elem2_list in pairs.items()
+            for elem2 in
+            (
+                # Ensure elem2_list is a list
+                [elem2_list] if isinstance(elem2_list, str) else elem2_list
+            )
+        ]
+    if not paired_elements:
+        raise log_error(
+            ValueError, "Could not find any pairs to compute distances for."
+        )
+    pairwise_distances = {
+        f"dist_{elem1}_{elem2}": _cdist(
+            data.sel({dim: elem1}),
+            data.sel({dim: elem2}),
+            dim=dim,
+            metric=metric,
+            **kwargs,
+        )
+        for elem1, elem2 in paired_elements
+    }
+    # Return DataArray if result only has one key
+    if len(pairwise_distances) == 1:
+        return next(iter(pairwise_distances.values()))
+    return pairwise_distances
+
+
+def _validate_labels_dimension(data: xr.DataArray, dim: str) -> xr.DataArray:
+    """Validate the input data contains the ``dim`` for labelling dimensions.
+
+    This function ensures the input data contains the ``dim``
+    used as labels (coordinates) when applying
+    :func:`scipy.spatial.distance.cdist` to
+    the input data, by adding a temporary dimension if necessary.
+
+    Parameters
+    ----------
+    data : xarray.DataArray
+        The input data to validate.
+    dim : str
+        The dimension to validate.
+
+    Returns
+    -------
+    xarray.DataArray
+        The input data with the labels dimension validated.
+
+    """
+    if data.coords.get(dim) is None:
+        data = data.assign_coords({dim: "temp_dim"})
+    if data.coords[dim].ndim == 0:
+        data = data.expand_dims(dim).transpose("time", "space", dim)
+    return data
+
+
 def _validate_type_data_array(data: xr.DataArray) -> None:
     """Validate the input data is an xarray DataArray.
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6da9a59..4de80c3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -567,6 +567,7 @@ def missing_two_dims_bboxes_dataset(valid_bboxes_dataset):
     return valid_bboxes_dataset.rename({"time": "tame", "space": "spice"})
 
 
+# --------------------------- Kinematics fixtures ---------------------------
 @pytest.fixture(params=["displacement", "velocity", "acceleration"])
 def kinematic_property(request):
     """Return a kinematic property."""
@@ -820,6 +821,7 @@ def track_ids_not_unique_per_frame(
     return file_path
 
 
+# ----------------- Helpers fixture -----------------
 class Helpers:
     """Generic helper methods for ``movement`` test modules."""
 
@@ -834,9 +836,6 @@ class Helpers:
         return (da.isnull().astype(int).diff("time") == 1).sum().item()
 
 
-# ----------------- Helper fixture -----------------
-
-
 @pytest.fixture
 def helpers():
     """Return an instance of the ``Helpers`` class."""
diff --git a/tests/test_unit/test_kinematics.py b/tests/test_unit/test_kinematics.py
index 4c529f2..18cfa2d 100644
--- a/tests/test_unit/test_kinematics.py
+++ b/tests/test_unit/test_kinematics.py
@@ -349,3 +349,209 @@ def test_nan_behavior_forward_vector(
         np.isnan(forward_vector.values[1, 0, :]).all()
         and not np.isnan(forward_vector.values[[0, 2, 3], 0, :]).any()
     )
+
+
+@pytest.mark.parametrize(
+    "dim, expected_data",
+    [
+        (
+            "individuals",
+            np.array(
+                [
+                    [
+                        [0.0, 1.0, 1.0],
+                        [1.0, np.sqrt(2), 0.0],
+                        [1.0, 2.0, np.sqrt(2)],
+                    ],
+                    [
+                        [2.0, np.sqrt(5), 1.0],
+                        [3.0, np.sqrt(10), 2.0],
+                        [np.sqrt(5), np.sqrt(8), np.sqrt(2)],
+                    ],
+                ]
+            ),
+        ),
+        (
+            "keypoints",
+            np.array(
+                [[[1.0, 1.0], [1.0, 1.0]], [[1.0, np.sqrt(5)], [3.0, 1.0]]]
+            ),
+        ),
+    ],
+)
+def test_cdist_with_known_values(
+    dim, expected_data, valid_poses_dataset_uniform_linear_motion
+):
+    """Test the computation of pairwise distances with known values."""
+    labels_dim = "keypoints" if dim == "individuals" else "individuals"
+    input_dataarray = valid_poses_dataset_uniform_linear_motion.position.sel(
+        time=slice(0, 1)
+    )  # Use only the first two frames for simplicity
+    pairs = input_dataarray[dim].values[:2]
+    expected = xr.DataArray(
+        expected_data,
+        coords=[
+            input_dataarray.time.values,
+            getattr(input_dataarray, labels_dim).values,
+            getattr(input_dataarray, labels_dim).values,
+        ],
+        dims=["time", pairs[0], pairs[1]],
+    )
+    a = input_dataarray.sel({dim: pairs[0]})
+    b = input_dataarray.sel({dim: pairs[1]})
+    result = kinematics._cdist(a, b, dim)
+    xr.testing.assert_equal(
+        result,
+        expected,
+    )
+
+
+@pytest.mark.parametrize(
+    "valid_dataset",
+    [
+        "valid_poses_dataset_uniform_linear_motion",
+        "valid_bboxes_dataset",
+    ],
+)
+@pytest.mark.parametrize(
+    "selection_fn",
+    [
+        # individuals dim is scalar,
+        # poses: multiple keypoints
+        # bboxes: missing keypoints dim
+        # e.g. comparing 2 individuals from the same data array
+        lambda position: (
+            position.isel(individuals=0),
+            position.isel(individuals=1),
+        ),
+        # individuals dim is 1D
+        # poses: multiple keypoints
+        # bboxes: missing keypoints dim
+        # e.g. comparing 2 single-individual data arrays
+        lambda position: (
+            position.where(
+                position.individuals == position.individuals[0], drop=True
+            ).squeeze(),
+            position.where(
+                position.individuals == position.individuals[1], drop=True
+            ).squeeze(),
+        ),
+        # both individuals and keypoints dims are scalar (poses only)
+        # e.g. comparing 2 individuals from the same data array,
+        # at the same keypoint
+        lambda position: (
+            position.isel(individuals=0, keypoints=0),
+            position.isel(individuals=1, keypoints=0),
+        ),
+        # individuals dim is scalar, keypoints dim is 1D (poses only)
+        # e.g. comparing 2 single-individual, single-keypoint data arrays
+        lambda position: (
+            position.where(
+                position.keypoints == position.keypoints[0], drop=True
+            ).isel(individuals=0),
+            position.where(
+                position.keypoints == position.keypoints[0], drop=True
+            ).isel(individuals=1),
+        ),
+    ],
+    ids=[
+        "dim_has_ndim_0",
+        "dim_has_ndim_1",
+        "labels_dim_has_ndim_0",
+        "labels_dim_has_ndim_1",
+    ],
+)
+def test_cdist_with_single_dim_inputs(valid_dataset, selection_fn, request):
+    """Test that the pairwise distances data array is successfully
+     returned regardless of whether the input DataArrays have
+    ``dim`` ("individuals") and ``labels_dim`` ("keypoints")
+    being either scalar (ndim=0) or 1D (ndim=1),
+    or if ``labels_dim`` is missing.
+    """
+    if request.node.callspec.id not in [
+        "labels_dim_has_ndim_0-valid_bboxes_dataset",
+        "labels_dim_has_ndim_1-valid_bboxes_dataset",
+    ]:  # Skip tests with keypoints dim for bboxes
+        valid_dataset = request.getfixturevalue(valid_dataset)
+        position = valid_dataset.position
+        a, b = selection_fn(position)
+        assert isinstance(kinematics._cdist(a, b, "individuals"), xr.DataArray)
+
+
+@pytest.mark.parametrize(
+    "dim, pairs, expected_data_vars",
+    [
+        ("individuals", {"id_1": ["id_2"]}, None),  # list input
+        ("individuals", {"id_1": "id_2"}, None),  # string input
+        (
+            "individuals",
+            {"id_1": ["id_2"], "id_2": "id_1"},
+            [("id_1", "id_2"), ("id_2", "id_1")],
+        ),
+        ("individuals", "all", None),  # all pairs
+        ("keypoints", {"centroid": ["left"]}, None),  # list input
+        ("keypoints", {"centroid": "left"}, None),  # string input
+        (
+            "keypoints",
+            {"centroid": ["left"], "left": "right"},
+            [("centroid", "left"), ("left", "right")],
+        ),
+        (
+            "keypoints",
+            "all",
+            [("centroid", "left"), ("centroid", "right"), ("left", "right")],
+        ),  # all pairs
+    ],
+)
+def test_compute_pairwise_distances_with_valid_pairs(
+    valid_poses_dataset_uniform_linear_motion, dim, pairs, expected_data_vars
+):
+    """Test that the expected pairwise distances are computed
+    for valid ``pairs`` inputs.
+    """
+    result = kinematics.compute_pairwise_distances(
+        valid_poses_dataset_uniform_linear_motion.position, dim, pairs
+    )
+    if isinstance(result, dict):
+        expected_data_vars = [
+            f"dist_{pair[0]}_{pair[1]}" for pair in expected_data_vars
+        ]
+        assert set(result.keys()) == set(expected_data_vars)
+    else:  # expect single DataArray
+        assert isinstance(result, xr.DataArray)
+
+
+@pytest.mark.parametrize(
+    "ds, dim, pairs",
+    [
+        (
+            "valid_poses_dataset_uniform_linear_motion",
+            "invalid_dim",
+            {"id_1": "id_2"},
+        ),  # invalid dim
+        (
+            "valid_poses_dataset_uniform_linear_motion",
+            "keypoints",
+            "invalid_string",
+        ),  # invalid pairs
+        (
+            "valid_poses_dataset_uniform_linear_motion",
+            "individuals",
+            {},
+        ),  # empty pairs
+        ("missing_dim_poses_dataset", "keypoints", "all"),  # invalid dataset
+        (
+            "missing_dim_bboxes_dataset",
+            "individuals",
+            "all",
+        ),  # invalid dataset
+    ],
+)
+def test_compute_pairwise_distances_with_invalid_input(
+    ds, dim, pairs, request
+):
+    """Test that an error is raised for invalid inputs."""
+    with pytest.raises(ValueError):
+        kinematics.compute_pairwise_distances(
+            request.getfixturevalue(ds).position, dim, pairs
+        )