Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ Examples/reprocessDataServer.py
*.stats

newsletter.py

.DS_Store
.vscode/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/opencap-test-data"]
path = tests/opencap-test-data
url = https://github.com/stanfordnmbl/opencap-test-data.git
1 change: 1 addition & 0 deletions defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFAULT_SYNC_VER = '1.0'
12 changes: 9 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
from utilsChecker import calcExtrinsicsFromVideo
from utilsChecker import isCheckerboardUpsideDown
from utilsChecker import autoSelectExtrinsicSolution
from utilsChecker import synchronizeVideos
from utilsChecker import triangulateMultiviewVideo
from utilsChecker import writeTRCfrom3DKeypoints
from utilsChecker import popNeutralPoseImages
from utilsChecker import rotateIntrinsics
from utilsSync import synchronizeVideos
from utilsDetector import runPoseDetector
from utilsAugmenter import augmentTRC
from utilsOpenSim import runScaleTool, getScaleTimeRange, runIKTool, generateVisualizerJson
from defaults import DEFAULT_SYNC_VER

def main(sessionName, trialName, trial_id, cameras_to_use=['all'],
intrinsicsFinalFolder='Deployed', isDocker=False,
Expand All @@ -42,7 +43,7 @@ def main(sessionName, trialName, trial_id, cameras_to_use=['all'],
dataDir=None, overwriteAugmenterModel=False,
filter_frequency='default', overwriteFilterFrequency=False,
scaling_setup='upright_standing_pose', overwriteScalingSetup=False,
overwriteCamerasToUse=False):
overwriteCamerasToUse=False, syncVer=None,):

# %% High-level settings.
# Camera calibration.
Expand Down Expand Up @@ -131,6 +132,10 @@ def main(sessionName, trialName, trial_id, cameras_to_use=['all'],
else:
camerasToUse = cameras_to_use

# We'll use syncVer if provided to this function. If not, try to use one
# from sessionMetadata, otherwise use the default one.
syncVer = syncVer or sessionMetadata.get('sync_ver', DEFAULT_SYNC_VER)

# %% Paths to pose detector folder for local testing.
if poseDetector == 'OpenPose':
poseDetectorDirectory = getOpenPoseDirectory(isDocker)
Expand Down Expand Up @@ -392,7 +397,8 @@ def main(sessionName, trialName, trial_id, cameras_to_use=['all'],
filtFreqs=filtFreqs, confidenceThreshold=0.4,
imageBasedTracker=False, cams2Use=camerasToUse_c,
poseDetector=poseDetector, trialName=trialName,
resolutionPoseDetection=resolutionPoseDetection))
resolutionPoseDetection=resolutionPoseDetection,
syncVer=syncVer))
except Exception as e:
if len(e.args) == 2: # specific exception
raise Exception(e.args[0], e.args[1])
Expand Down
1 change: 1 addition & 0 deletions tests/opencap-test-data
Submodule opencap-test-data added at 304ab5
129 changes: 65 additions & 64 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,79 @@
import logging
import os
import sys
import pytest
import requests
from unittest.mock import patch, Mock, ANY
from unittest.mock import patch, Mock
from http.client import HTTPMessage

thisDir = os.path.dirname(os.path.realpath(__file__))
repoDir = os.path.abspath(os.path.join(thisDir,'../'))
sys.path.append(repoDir)
from utils import makeRequestWithRetry

class TestMakeRequestWithRetry:
logging.getLogger('urllib3').setLevel(logging.DEBUG)
logging.getLogger('urllib3').setLevel(logging.DEBUG)

@patch("requests.Session.request")
def test_get(self, mock_response):
status_code = 200
mock_response.return_value.status_code = status_code
@patch("requests.Session.request")
def test_get(mock_response):
status_code = 200
mock_response.return_value.status_code = status_code

response = makeRequestWithRetry('GET', 'https://test.com', retries=2)
assert response.status_code == status_code
mock_response.assert_called_once_with('GET', 'https://test.com',
headers=None,
data=None,
params=None,
files=None)
response = makeRequestWithRetry('GET', 'https://test.com', retries=2)
assert response.status_code == status_code
mock_response.assert_called_once_with('GET', 'https://test.com',
headers=None,
data=None,
params=None,
files=None)

@patch("requests.Session.request")
def test_put(self, mock_response):
status_code = 201
mock_response.return_value.status_code = status_code

data = {
"key1": "value1",
"key2": "value2"
}
@patch("requests.Session.request")
def test_put(mock_response):
status_code = 201
mock_response.return_value.status_code = status_code

params = {
"param1": "value1"
}

response = makeRequestWithRetry('POST',
'https://test.com',
data=data,
headers={"Authorization": "my_token"},
params=params,
retries=2)

assert response.status_code == status_code
mock_response.assert_called_once_with('POST',
'https://test.com',
data=data,
headers={"Authorization": "my_token"},
params=params,
files=None)
data = {
"key1": "value1",
"key2": "value2"
}

@patch("urllib3.connectionpool.HTTPConnectionPool._get_conn")
def test_success_after_retries(self, mock_response):
mock_response.return_value.getresponse.side_effect = [
Mock(status=500, msg=HTTPMessage()),
Mock(status=502, msg=HTTPMessage()),
Mock(status=200, msg=HTTPMessage()),
Mock(status=429, msg=HTTPMessage()),
]
params = {
"param1": "value1"
}

response = makeRequestWithRetry('GET',
'https://test.com',
retries=5,
backoff_factor=0.1)
response = makeRequestWithRetry('POST',
'https://test.com',
data=data,
headers={"Authorization": "my_token"},
params=params,
retries=2)

assert response.status_code == 200
assert mock_response.call_count == 3
assert response.status_code == status_code
mock_response.assert_called_once_with('POST',
'https://test.com',
data=data,
headers={"Authorization": "my_token"},
params=params,
files=None)

# comment out test since httpbin can be unstable and we don't want to rely
# on it for tests. uncomment and see debug log to see retry attempts
'''def test_httpbin(self):
response = makeRequestWithRetry('GET',
'https://httpbin.org/status/500',
retries=4,
backoff_factor=0.1)
'''
@patch("urllib3.connectionpool.HTTPConnectionPool._get_conn")
def test_success_after_retries(mock_response):
Copy link
Member

Choose a reason for hiding this comment

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

Failed for me in Windows, the output gives:

(opencap) PS C:\Workspaces\opencap\opencap-core> 
================================================================================= test session starts =================================================================================
platform win32 -- Python 3.9.23, pytest-8.4.2, pluggy-1.6.0
rootdir: C:\Workspaces\opencap\opencap-core
collected 1 item                                                                                                                                                                       
run-last-failure: rerun previous 1 failure (skipped 2 files)

tests\test_api.py F                                                                                                                                                              [100%]

====================================================================================== FAILURES =======================================================================================
_____________________________________________________________________________ test_success_after_retries ______________________________________________________________________________

mock_response = <MagicMock name='_get_conn' id='2936532450464'>

    @patch("urllib3.connectionpool.HTTPConnectionPool._get_conn")
    def test_success_after_retries(mock_response):
        mock_response.return_value.getresponse.side_effect = [
            Mock(status=500, msg=HTTPMessage()),
            Mock(status=502, msg=HTTPMessage()),
            Mock(status=200, msg=HTTPMessage()),
            Mock(status=429, msg=HTTPMessage()),
        ]

>       response = makeRequestWithRetry('GET',
                                        'https://test.com/',
                                        retries=5,
                                        backoff_factor=0.1)

tests\test_api.py:66:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
utils.py:1765: in makeRequestWithRetry
    response = session.request(method,
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\requests\sessions.py:589: in request
    resp = self.send(prep, **send_kwargs)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\requests\sessions.py:703: in send
    r = adapter.send(request, **kwargs)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\requests\adapters.py:644: in send
    resp = conn.urlopen(
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\urllib3\connectionpool.py:946: in urlopen
    retries.sleep(response)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\urllib3\util\retry.py:355: in sleep
    slept = self.sleep_for_retry(response)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\urllib3\util\retry.py:332: in sleep_for_retry
    retry_after = self.get_retry_after(response)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\urllib3\util\retry.py:329: in get_retry_after
    return self.parse_retry_after(retry_after)
C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\urllib3\util\retry.py:307: in parse_retry_after
    if re.match(r"^\s*[0-9]+\s*$", retry_after):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

pattern = '^\\s*[0-9]+\\s*$', string = <Mock name='mock.headers.get()' id='2936533140720'>, flags = 0

    def match(pattern, string, flags=0):
        """Try to apply the pattern at the start of the string, returning
        a Match object, or None if no match was found."""
>       return _compile(pattern, flags).match(string)
E       TypeError: expected string or bytes-like object

C:\Users\NMBL\anaconda3\envs\opencap\lib\re.py:191: TypeError
---------------------------------------------------------------------------------- Captured log call ---------------------------------------------------------------------------------- 
DEBUG    urllib3.connectionpool:connectionpool.py:549 [https://test.com:443](https://test.com/) "GET / <MagicMock name='_get_conn()._http_vsn_str' id='2936533074656'>" 500 <Mock name='mock.length_remaining' id='2936533090704'>
DEBUG    urllib3.util.retry:retry.py:517 Incremented Retry for (url='/'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
================================================================================== warnings summary =================================================================================== 
..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:10
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:10: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _nlv = LooseVersion(_np_version)

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:11
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:11: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p16 = _nlv < LooseVersion("1.16")

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:12
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:12: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p17 = _nlv < LooseVersion("1.17")

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:13
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:13: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p18 = _nlv < LooseVersion("1.18")

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:14
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:14: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p19 = _nlv < LooseVersion("1.19")

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:15
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\__init__.py:15: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    _np_version_under1p20 = _nlv < LooseVersion("1.20")

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\setuptools\_distutils\version.py:336
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\setuptools\_distutils\version.py:336: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    other = LooseVersion(other)

..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\function.py:125
..\..\..\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\function.py:125
  C:\Users\NMBL\anaconda3\envs\opencap\lib\site-packages\pandas\compat\numpy\function.py:125: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    if LooseVersion(_np_version) >= LooseVersion("1.17.0"):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=============================================================================== short test summary info =============================================================================== 
FAILED tests/test_api.py::test_success_after_retries - TypeError: expected string or bytes-like object
============================================================================ 1 failed, 9 warnings in 3.04s ============================================================================ 
(opencap) PS C:\Workspaces\opencap\opencap-core>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could not replicate this on a local machine on Windows, Mac or Linux. The patch response is indeed quite far in, so it's tricky (I tried a little bit to simplify it, but the hook is very deep for the retry). Let me know if you're OK with it as is, or one option would be to remove the test (it's mostly an off-the-shelf use case of the requests library).

Copy link
Member

Choose a reason for hiding this comment

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

If that works for you and Matt I think it should be OK. I had a hard time setting out my environment, so it was probably something related to my local setting.

mock_response.return_value.getresponse.side_effect = [
Mock(status=500, msg=HTTPMessage()),
Mock(status=502, msg=HTTPMessage()),
Mock(status=200, msg=HTTPMessage()),
Mock(status=429, msg=HTTPMessage()),
]

response = makeRequestWithRetry('GET',
'https://test.com',
retries=5,
backoff_factor=0.1)

assert response.status_code == 200
assert mock_response.call_count == 3

# The httpbin test remains commented out for stability reasons
# def test_httpbin():
# response = makeRequestWithRetry('GET',
# 'https://httpbin.org/status/500',
# retries=4,
# backoff_factor=0.1)
147 changes: 147 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import logging
Copy link
Collaborator

Choose a reason for hiding this comment

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

Tests failed on my Windows machine. Here is the output:

PASSED tests/test_api.py::test_get
PASSED tests/test_api.py::test_put
PASSED tests/test_api.py::test_success_after_retries
PASSED tests/test_sync.py::test_synchronize_videos[squats-Using general sync function.-1.0]
PASSED tests/test_sync.py::test_synchronize_videos[squats-Using general sync function.-1.1]
PASSED tests/test_sync.py::test_synchronize_videos[walk-Using gait sync function.-1.0]
PASSED tests/test_sync.py::test_synchronize_videos[walk-Using gait sync function.-1.1]
PASSED tests/test_sync.py::test_synchronize_videos[squats-with-arm-raise-Using handPunch sync function.-1.0]
PASSED tests/test_sync.py::test_synchronize_videos[squats-with-arm-raise-Using handPunch sync function.-1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_clean_right_or_left[1.0-r-punch_range0-None]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_clean_right_or_left[1.1-r-punch_range1-ref_handPunchRange1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_clean_right_or_left[1.0-l-punch_range2-None]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_clean_right_or_left[1.1-l-punch_range3-ref_handPunchRange3]
PASSED tests/test_sync.py::TestDetectHandPunch::test_no_punch[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_no_punch[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_both_hands_same_time[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_both_hands_same_time[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_both_hands_different_times[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_both_hands_different_times[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_multiple_punches_different_height[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_multiple_punches_different_height[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_multiple_punches_similar_height[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_multiple_punches_similar_height[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_low_confidence[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_low_confidence[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_too_short[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_too_short[1.1]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_too_long[1.0]
PASSED tests/test_sync.py::TestDetectHandPunch::test_punch_too_long[1.1]
PASSED tests/test_sync.py::TestSyncHandPunch::test_syncver_lag_between_cameras[1.0-5-5]
PASSED tests/test_sync.py::TestSyncHandPunch::test_syncver_lag_between_cameras[1.1-5-5]
PASSED tests/test_sync.py::TestSyncHandPunch::test_syncver_lag_between_cameras[1.0-120-103]
PASSED tests/test_sync.py::TestSyncHandPunch::test_syncver_lag_between_cameras[1.1-120-120]
PASSED tests/test_sync.py::TestSyncHandPunch::test_sync_hand_punch_v2_stability[position-None]
PASSED tests/test_sync.py::TestSyncHandPunch::test_sync_hand_punch_v2_stability[position-6.0]
PASSED tests/test_sync.py::TestSyncHandPunch::test_sync_hand_punch_v2_stability[velocity-None]
PASSED tests/test_sync.py::TestSyncHandPunch::test_sync_hand_punch_v2_stability[velocity-6.0]
FAILED tests/test_main.py::test_main[squats-with-arm-raise-5.0-10.0-1.0] - AssertionError: DataFrame.iloc[:, 3] (column name=“3”) are different
FAILED tests/test_main.py::test_main[squats-with-arm-raise-5.0-10.0-1.1] - AssertionError: DataFrame.iloc[:, 3] (column name=“3") are different
FAILED tests/test_main.py::test_main[squats-3.0-8.0-1.0] - AssertionError: DataFrame.iloc[:, 3] (column name=“3”) are different
FAILED tests/test_main.py::test_main[squats-3.0-8.0-1.1] - AssertionError: DataFrame.iloc[:, 3] (column name=“3") are different
FAILED tests/test_main.py::test_main[walk-1.0-5.0-1.0] - AssertionError: DataFrame.iloc[:, 3] (column name=“3”) are different
FAILED tests/test_main.py::test_main[walk-1.0-5.0-1.1] - AssertionError: DataFrame.iloc[:, 3] (column name=“3") are different
========================================================================================================================================================================================== 6 failed, 37 passed, 19 warnings in 77.19s (0:01:17) ==========================================================================================================================================================================================

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the test to be looser (markers post-augmenter should now checked within 1mm per frame) and tested on a Windows machine that this passes on there now. Probably worth checking locally on your machine too.

Copy link
Collaborator

Choose a reason for hiding this comment

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

All tests pass now on my Windows machine.

import os
import sys
import numpy as np
import pandas as pd
import pytest

thisDir = os.path.dirname(os.path.realpath(__file__))
repoDir = os.path.abspath(os.path.join(thisDir, '../'))
sys.path.append(repoDir)
from main import main

# Helper functions to load and compare TRC and MOT files
def load_trc(file, num_metadata_lines=5):
with open(file, 'r') as f:
lines = f.readlines()
metadata = lines[:num_metadata_lines]
df = pd.read_csv(file, sep='\t', skiprows=num_metadata_lines + 1, header=None)
return df, metadata

def load_mot(file, num_metadata_lines=10):
with open(file, 'r') as f:
lines = f.readlines()
metadata = lines[:num_metadata_lines]
df = pd.read_csv(file, sep='\t', skiprows=num_metadata_lines)
return df, metadata


def calc_rmse(series1, series2):
return np.sqrt(((series1 - series2) ** 2).mean())

def compare_mot(output_mot_df, ref_mot_df, t0, tf):
'''Function to compare MOT dataframes within a time range [t0, tf].
We use the specific time range to analyze the range with the motion
of interest. In particular, the arm raise can create larger differences
on single frames.

- Time column is checked for equality (IK is frame-by-frame).
- Translation error is checked within 2 mm max per frame, RMSE within
1 mm.
- Rotation error for wrist pronation/supination (coordinates pro_sup_r
and pro_sup_l) are checked within 5.0 degrees max per frame, RMSE
within 1.0 degrees.
- Rotation error for all other coordinates are tighter and checked
within 2.5 degrees max per frame, RMSE within 0.5 degrees.
'''
output_mot_df_slice = output_mot_df[(output_mot_df['time'] >= t0) & (output_mot_df['time'] <= tf)]
ref_mot_df_slice = ref_mot_df[(ref_mot_df['time'] >= t0) & (ref_mot_df['time'] <= tf)]
for col in ref_mot_df.columns:
# time column should be equal since IK is frame-by-frame
if col == 'time':
pd.testing.assert_series_equal(output_mot_df[col], ref_mot_df[col])

# check translational within 2 mm max error, rmse within 1 mm
elif any(substr in col for substr in ['tx', 'ty', 'tz']):
pd.testing.assert_series_equal(
output_mot_df_slice[col], ref_mot_df_slice[col], atol=0.002
)
rmse = calc_rmse(output_mot_df_slice[col], ref_mot_df_slice[col])
assert rmse <= 0.001

elif 'pro_sup' in col:
pd.testing.assert_series_equal(
output_mot_df_slice[col], ref_mot_df_slice[col], atol=5.0
)
rmse = calc_rmse(output_mot_df_slice[col], ref_mot_df_slice[col])
assert rmse <= 1.0

# check rotational within 2.5 degrees max error, rmse within 0.5 degrees
else:
pd.testing.assert_series_equal(
output_mot_df_slice[col], ref_mot_df_slice[col], atol=2.5
)
rmse = calc_rmse(output_mot_df_slice[col], ref_mot_df_slice[col])
assert rmse <= 0.5

# End to end tests with different sync methods (hand, gait, general).
# Also check that syncVer updates with main changes.
# Note: no pose detection, uses pre-scaled opensim model
@pytest.mark.parametrize("syncVer", ['1.0', '1.1'])
@pytest.mark.parametrize("trialName, t0, tf", [
('squats-with-arm-raise', 5.0, 10.0),
('squats', 3.0, 8.0),
('walk', 1.0, 5.0),
])
def test_main(trialName, t0, tf, syncVer, caplog):
caplog.set_level(logging.INFO)

sessionName = 'sync_2-cameras'
trialID = trialName
dataDir = os.path.join(thisDir, 'opencap-test-data')
main(
sessionName,
trialName,
trialID,
dataDir=dataDir,
genericFolderNames=True,
poseDetector='hrnet',
syncVer=syncVer,
)
assert f"Synchronizing Keypoints using version {syncVer}" in caplog.text

# Compare marker data
output_trc = os.path.join(dataDir,
'Data',
sessionName,
'MarkerData',
'PostAugmentation',
f'{trialName}.trc',
)
ref_trc = os.path.join(
dataDir,
'Data',
sessionName,
'OutputReference',
f'{trialName}.trc',
)
output_trc_df, _ = load_trc(output_trc)
ref_trc_df, _ = load_trc(ref_trc)
pd.testing.assert_frame_equal(
output_trc_df, ref_trc_df, check_exact=False, atol=1e-3
)

# Compare IK data
output_mot = os.path.join(
dataDir,
'Data',
sessionName,
'OpenSimData',
'Kinematics',
f'{trialName}.mot',
)
ref_mot = os.path.join(
dataDir,
'Data',
sessionName,
'OutputReference',
f'{trialName}.mot',
)
output_mot_df, _ = load_mot(output_mot)
ref_mot_df, _ = load_mot(ref_mot)
pd.testing.assert_index_equal(output_mot_df.columns, ref_mot_df.columns)
compare_mot(output_mot_df, ref_mot_df, t0, tf)

# TODO: calibration and neutral
# TODO: > 2 cameras
# TODO: augmenter versions
Loading