Skip to content

Commit 99fbb58

Browse files
authored
Merge pull request #34 from EveCharbie/env
Fixed the environment issues (some dependency updates changed the test values, so I made the scaling test less sensitive and changed the joint center tool tests values).
2 parents 8daa5ad + 63e7a60 commit 99fbb58

File tree

14 files changed

+292
-667
lines changed

14 files changed

+292
-667
lines changed

.github/workflows/copilot-setup-steps.yml

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
2121
copilot-setup-steps:
2222
runs-on: ubuntu-latest
23+
defaults:
24+
run:
25+
shell: bash -l {0}
2326
steps:
2427
- name: Checkout code with submodule
2528
uses: actions/checkout@v4
@@ -31,19 +34,29 @@ jobs:
3134
uses: conda-incubator/setup-miniconda@v3
3235
with:
3336
miniforge-version: latest
34-
35-
- name: Set up Python
36-
run: |
37-
conda install -cconda-forge python=3.11.11 pip
37+
activate-environment: biobuddy
38+
python-version: 3.11.11
3839

3940
- name: Install dependencies
4041
run: |
4142
pip install . && pip uninstall -y biobuddy
42-
pip install scipy==1.15.1 numpy lxml ezc3d
43+
pip install scipy==1.15.2 numpy==1.25.2 lxml ezc3d
4344
4445
- name: Install test dependencies
4546
run: |
46-
pip install pytest pytest-cov codecov pyomeca black
47+
pip install pytest pytest-cov codecov black
4748
conda install -c opensim-org opensim=4.5.1
4849
conda install -c conda-forge biorbd=1.11.2 deepdiff
49-
50+
51+
- name: Set environment variables for Copilot
52+
run: |
53+
echo "CONDA_PREFIX=$CONDA_PREFIX" >> $GITHUB_ENV
54+
echo "PYTHONPATH=$CONDA_PREFIX/lib/python3.11/site-packages:$PYTHONPATH" >> $GITHUB_ENV
55+
echo "PATH=$CONDA_PREFIX/bin:$PATH" >> $GITHUB_ENV
56+
57+
- name: Verify environment setup
58+
run: |
59+
echo "Python location: $(which python)"
60+
echo "Conda prefix: $CONDA_PREFIX"
61+
echo "Python path: $PYTHONPATH"
62+
python -c "import sys; print('Python sys.path:'); [print(f' {p}') for p in sys.path]"

.github/workflows/run_tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ jobs:
6262
- name: Install dependencies
6363
run: |
6464
pip install . && pip uninstall -y biobuddy
65-
pip install scipy==1.15.1 numpy==1.25.2 lxml ezc3d
65+
pip install scipy==1.15.1 numpy==1.25.2 lxml ezc3d matplotlib
6666
6767
- name: Install test dependencies
6868
run: |
69-
pip install pytest pytest-cov codecov pyomeca
69+
pip install pytest pytest-cov codecov
7070
conda install -c opensim-org opensim=4.5.1
7171
conda install -c conda-forge biorbd=1.11.2 deepdiff
7272

README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ If you are a user, you can set up your environment with minimal dependencies.
1919
conda install -c conda-forge python=3.11.11 pip
2020
pip install scipy==1.15.1 numpy==1.25.2 lxml ezc3d
2121
```
22+
Note: On mac, you might need to add `conda install conda-forge::libcxx`
2223

2324
However, if you are a developer and want to contribute, you will need to set up your environment using the following command:
2425
Due to the OpenSim dependency used only in BioBuddy's tests, we recommend using python=3.11.
2526
```bash
26-
pip install pytest pytest-cov codecov pyomeca
27+
pip install pytest pytest-cov codecov
2728
conda install -c opensim-org opensim=4.5.1
2829
conda install -c conda-forge biorbd=1.11.2 deepdiff
2930
```
@@ -60,7 +61,7 @@ from biobuddy import ScaleTool
6061
scale_tool = ScaleTool(original_model=original_model).from_xml(filepath=xml_filepath)
6162

6263
# Performing the scaling based on a static trial
63-
scaled_model_CAC = scale_tool.scale(filepath=static_c3d_filepath, first_frame=100, last_frame=200, mass=mass)
64+
scaled_model = scale_tool.scale(static_c3d=C3dData(filepath), mass=mass)
6465
```
6566

6667
**Joint center identification:**
@@ -75,25 +76,21 @@ joint_center_tool = JointCenterTool(scaled_model)
7576
# Example for the right hip
7677
joint_center_tool.add(
7778
Score(
78-
filepath=hip_movement_c3d_filepath,
79+
functional_c3d=C3dData(c3d_filepath, first_frame=100, last_frame=900),
7980
parent_name="pelvis",
8081
child_name="femur_r",
8182
parent_marker_names=["RASIS", "LASIS", "LPSIS", "RPSIS"],
8283
child_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg", "RMFE", "RLFE"],
83-
first_frame=100,
84-
last_frame=900,
8584
)
8685
)
8786
# Example for the right knee
8887
joint_center_tool.add(
8988
Sara(
90-
filepath=knee_movement_c3d_filepath,
89+
functional_c3d=C3dData(c3d_filepath),
9190
parent_name="femur_r",
9291
child_name="tibia_r",
9392
parent_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg"],
9493
child_marker_names=["RATT", "RUB_Tib", "RDF_Tib", "RDB_Tib", "RSPH", "RLM"],
95-
first_frame=100,
96-
last_frame=900,
9794
)
9895
)
9996

biobuddy/model_modifiers/joint_center_tool.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from ..utils.c3d_data import C3dData
1313
from ..utils.linear_algebra import (
1414
RotoTransMatrix,
15-
get_closest_rt_matrix,
1615
mean_unit_vector,
1716
RotoTransMatrixTimeSeries,
1817
point_from_local_to_global,
@@ -27,21 +26,19 @@
2726
class RigidSegmentIdentification:
2827
def __init__(
2928
self,
30-
filepath: str,
29+
functional_c3d: C3dData,
3130
parent_name: str,
3231
child_name: str,
3332
parent_marker_names: list[str],
3433
child_marker_names: list[str],
35-
first_frame: int = None,
36-
last_frame: int = None,
3734
initialize_whole_trial_reconstruction: bool = False,
3835
animate_rt: bool = False,
3936
):
4037
"""
4138
Parameters
4239
----------
43-
filepath
44-
The path to the .c3d file containing the functional trial.
40+
functional_c3d
41+
The .c3d file containing the functional trial.
4542
parent_name
4643
The name of the joint's parent segment.
4744
child_name
@@ -50,24 +47,18 @@ def __init__(
5047
The name of the markers in the parent segment to consider during the SCoRE algorithm.
5148
child_marker_names
5249
The name of the markers in the child segment to consider during the SCoRE algorithm.
53-
first_frame
54-
The first frame to consider in the functional trial.
55-
last_frame
56-
The last frame to consider in the functional trial.
5750
initialize_whole_trial_reconstruction
5851
If True, the whole trial is reconstructed using whole body inverse kinematics to initialize the segments' rt in the global reference frame.
5952
animate_rt
6053
If True, it animates the segment rt reconstruction using pyomeca and pyorerun.
6154
"""
6255

6356
# Original attributes
64-
self.filepath = filepath
57+
self.c3d_data = functional_c3d
6558
self.parent_name = parent_name
6659
self.child_name = child_name
6760
self.parent_marker_names = parent_marker_names
6861
self.child_marker_names = child_marker_names
69-
self.first_frame = first_frame
70-
self.last_frame = last_frame
7162
self.initialize_whole_trial_reconstruction = initialize_whole_trial_reconstruction
7263
self.animate_rt = animate_rt
7364

@@ -78,7 +69,6 @@ def __init__(
7869
self.child_static_markers_in_local: np.ndarray = None
7970
self.parent_markers_global: np.ndarray = None
8071
self.child_markers_global: np.ndarray = None
81-
self.c3d_data: C3dData = None
8272
self.marker_name: list[str] = None
8373
self.marker_positions: np.ndarray = None
8474

@@ -101,22 +91,15 @@ def _check_c3d_functional_trial_file(self):
10191
"""
10292
Check that the file format is appropriate and that there is a functional movement in the trial (aka the markers really move).
10393
"""
104-
# Check file format
105-
if self.filepath.endswith(".c3d"):
106-
# Load the c3d file
107-
self.c3d_data = C3dData(self.filepath, self.first_frame, self.last_frame)
108-
self.marker_names = self.c3d_data.marker_names
109-
self.marker_positions = self.c3d_data.all_marker_positions[:3, :, :]
110-
else:
111-
if self.filepath.endswith(".trc"):
112-
raise NotImplementedError(".trc files cannot be read yet.")
113-
else:
114-
raise RuntimeError("The filepath (static trial) must be a .c3d file in a static posture.")
94+
self.marker_names = self.c3d_data.marker_names
95+
self.marker_positions = self.c3d_data.all_marker_positions[:3, :, :]
11596

11697
# Check that the markers move
11798
std = []
11899
for marker_name in self.parent_marker_names + self.child_marker_names:
119100
std += self.c3d_data.std_marker_position(marker_name)
101+
if len(std) == 0:
102+
raise RuntimeError("There are no markers in the functional trial. Please check the trial again.")
120103
if all(np.array(std) < 0.01):
121104
raise RuntimeError(
122105
f"The markers {self.parent_marker_names + self.child_marker_names} are not moving in the functional trial (markers std = {std}). "
@@ -676,28 +659,24 @@ def perform_task(
676659
class Sara(RigidSegmentIdentification):
677660
def __init__(
678661
self,
679-
filepath: str,
662+
functional_c3d: C3dData,
680663
parent_name: str,
681664
child_name: str,
682665
parent_marker_names: list[str],
683666
child_marker_names: list[str],
684667
joint_center_markers: list[str],
685668
distal_markers: list[str],
686669
is_longitudinal_axis_from_jcs_to_distal_markers: bool,
687-
first_frame: int = None,
688-
last_frame: int = None,
689670
initialize_whole_trial_reconstruction: bool = False,
690671
animate_rt: bool = False,
691672
):
692673

693674
super(Sara, self).__init__(
694-
filepath=filepath,
675+
functional_c3d=functional_c3d,
695676
parent_name=parent_name,
696677
child_name=child_name,
697678
parent_marker_names=parent_marker_names,
698679
child_marker_names=child_marker_names,
699-
first_frame=first_frame,
700-
last_frame=last_frame,
701680
animate_rt=animate_rt,
702681
initialize_whole_trial_reconstruction=initialize_whole_trial_reconstruction,
703682
)

biobuddy/model_modifiers/scale_tool.py

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,7 @@ def add_marker_weight(self, marker_weight: MarkerWeight):
7070

7171
def scale(
7272
self,
73-
filepath: str,
74-
first_frame: int,
75-
last_frame: int,
73+
static_c3d: C3dData,
7674
mass: float,
7775
q_regularization_weight: float = None,
7876
initial_static_pose: np.ndarray = None,
@@ -85,12 +83,8 @@ def scale(
8583
8684
Parameters
8785
----------
88-
filepath
86+
static_c3d
8987
The .c3d or .trc file of the static trial to use for the scaling
90-
first_frame
91-
The index of the first frame to use in the .c3d file.
92-
last_frame
93-
The index of the last frame to use in the .c3d file.
9488
mass
9589
The mass of the subject
9690
q_regularization_weight
@@ -105,18 +99,9 @@ def scale(
10599
method
106100
The lease square method to use. (default: "lm", other options: "trf" or "dogbox")
107101
"""
102+
exp_marker_names = static_c3d.marker_names
103+
exp_marker_positions = static_c3d.all_marker_positions[:3, :, :]
108104

109-
# Check file format
110-
if filepath.endswith(".c3d"):
111-
# Load the c3d file
112-
c3d_data = C3dData(filepath, first_frame, last_frame)
113-
exp_marker_names = c3d_data.marker_names
114-
exp_marker_positions = c3d_data.all_marker_positions[:3, :, :]
115-
else:
116-
if filepath.endswith(".trc"):
117-
raise NotImplementedError(".trc files cannot be read yet.")
118-
else:
119-
raise RuntimeError("The filepath (static trial) must be a .c3d file in a static posture.")
120105
marker_indices = [idx for idx, m in enumerate(exp_marker_names) if m in self.original_model.marker_names]
121106
marker_names = [exp_marker_names[idx] for idx in marker_indices]
122107
marker_positions = exp_marker_positions[:, marker_indices, :]

biobuddy/utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .aliases import Point, Points
2-
from .c3d_data import C3dData
2+
from .c3d_data import C3dData, ReferenceFrame
33
from .protocols import Data, GenericDynamicModel
44
from .rotations import Rotations
55
from .translations import Translations
@@ -9,6 +9,7 @@
99
"Point",
1010
"Points",
1111
C3dData.__name__,
12+
ReferenceFrame.__name__,
1213
Data.__name__,
1314
GenericDynamicModel.__name__,
1415
Rotations.__name__,

biobuddy/utils/c3d_data.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
from enum import Enum
12
import numpy as np
23
import ezc3d
34

45

6+
class ReferenceFrame(Enum):
7+
"""
8+
The reference frame for the C3D data.
9+
"""
10+
11+
Z_UP = "z-up"
12+
Y_UP = "y-up"
13+
14+
515
class C3dData:
616
"""
717
Implementation of the `Data` protocol from model_creation
@@ -68,3 +78,46 @@ def _to_meter(self, data: np.array) -> np.ndarray:
6878
data /= factor
6979
data[3] = 1
7080
return data
81+
82+
def change_ref_frame(self, ref_from: ReferenceFrame, ref_to: ReferenceFrame) -> None:
83+
"""
84+
Change the reference frame of the data.
85+
"""
86+
if ref_from == ref_to:
87+
return
88+
89+
if ref_from == ReferenceFrame.Z_UP and ref_to == ReferenceFrame.Y_UP:
90+
temporary_data = self.ezc3d_data["data"]["points"].copy()
91+
self.ezc3d_data["data"]["points"][0, self.first_frame : self.last_frame, :] = temporary_data[
92+
0, self.first_frame : self.last_frame, :
93+
] # X = X
94+
self.ezc3d_data["data"]["points"][1, self.first_frame : self.last_frame, :] = temporary_data[
95+
2, self.first_frame : self.last_frame, :
96+
] # Y = Z
97+
self.ezc3d_data["data"]["points"][2, self.first_frame : self.last_frame, :] = -temporary_data[
98+
1, self.first_frame : self.last_frame, :
99+
] # Z = -Y
100+
101+
elif ref_from == ReferenceFrame.Y_UP and ref_to == ReferenceFrame.Z_UP:
102+
temporary_data = self.ezc3d_data["data"]["points"].copy()
103+
self.ezc3d_data["data"]["points"][0, self.first_frame : self.last_frame, :] = temporary_data[
104+
0, self.first_frame : self.last_frame, :
105+
] # X = X
106+
self.ezc3d_data["data"]["points"][1, self.first_frame : self.last_frame, :] = -temporary_data[
107+
2, self.first_frame : self.last_frame, :
108+
] # Y = -Z
109+
self.ezc3d_data["data"]["points"][2, self.first_frame : self.last_frame, :] = temporary_data[
110+
1, self.first_frame : self.last_frame, :
111+
] # Z = Y
112+
113+
else:
114+
raise ValueError(f"Cannot change from {ref_from} to {ref_to}.")
115+
116+
def save(self, new_path: str):
117+
"""
118+
Save the changes made to the C3D file.
119+
"""
120+
if "meta_points" in self.ezc3d_data["data"]:
121+
# Remove meta points if they exist as it might cause issues with some C3D writer
122+
del self.ezc3d_data["data"]["meta_points"]
123+
self.ezc3d_data.write(new_path)

environment.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ dependencies:
8080
- pillow==11.2.1
8181
- pluggy==1.6.0
8282
- pygments==2.19.1
83-
- pyomeca==2024.0.2
8483
- pyparsing==3.2.3
8584
- pytest==8.4.1
8685
- pytest-cov==6.2.1

examples/data/static.c3d

-588 KB
Binary file not shown.

0 commit comments

Comments
 (0)