biobuddy 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. biobuddy-0.1.0/LICENSE +21 -0
  2. biobuddy-0.1.0/PKG-INFO +274 -0
  3. biobuddy-0.1.0/README.md +244 -0
  4. biobuddy-0.1.0/biobuddy/__init__.py +31 -0
  5. biobuddy-0.1.0/biobuddy/characteristics/__init__.py +8 -0
  6. biobuddy-0.1.0/biobuddy/characteristics/de_leva.py +682 -0
  7. biobuddy-0.1.0/biobuddy/characteristics/from_original_model.py +22 -0
  8. biobuddy-0.1.0/biobuddy/components/__init__.py +18 -0
  9. biobuddy-0.1.0/biobuddy/components/functions.py +254 -0
  10. biobuddy-0.1.0/biobuddy/components/generic/__init__.py +12 -0
  11. biobuddy-0.1.0/biobuddy/components/generic/biomechanical_model.py +149 -0
  12. biobuddy-0.1.0/biobuddy/components/generic/muscle/__init__.py +10 -0
  13. biobuddy-0.1.0/biobuddy/components/generic/muscle/muscle.py +265 -0
  14. biobuddy-0.1.0/biobuddy/components/generic/muscle/muscle_group.py +89 -0
  15. biobuddy-0.1.0/biobuddy/components/generic/muscle/via_point.py +132 -0
  16. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/__init__.py +24 -0
  17. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/axis.py +110 -0
  18. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/contact.py +110 -0
  19. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/inertia_parameters.py +123 -0
  20. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/marker.py +140 -0
  21. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/mesh.py +91 -0
  22. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/mesh_file.py +157 -0
  23. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/range_of_motion.py +37 -0
  24. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/segment.py +176 -0
  25. biobuddy-0.1.0/biobuddy/components/generic/rigidbody/segment_coordinate_system.py +181 -0
  26. biobuddy-0.1.0/biobuddy/components/model_utils.py +229 -0
  27. biobuddy-0.1.0/biobuddy/components/muscle_utils.py +13 -0
  28. biobuddy-0.1.0/biobuddy/components/real/__init__.py +12 -0
  29. biobuddy-0.1.0/biobuddy/components/real/biomechanical_model_real.py +393 -0
  30. biobuddy-0.1.0/biobuddy/components/real/model_dynamics.py +768 -0
  31. biobuddy-0.1.0/biobuddy/components/real/muscle/__init__.py +10 -0
  32. biobuddy-0.1.0/biobuddy/components/real/muscle/muscle_group_real.py +101 -0
  33. biobuddy-0.1.0/biobuddy/components/real/muscle/muscle_real.py +298 -0
  34. biobuddy-0.1.0/biobuddy/components/real/muscle/via_point_real.py +126 -0
  35. biobuddy-0.1.0/biobuddy/components/real/rigidbody/__init__.py +32 -0
  36. biobuddy-0.1.0/biobuddy/components/real/rigidbody/axis_real.py +55 -0
  37. biobuddy-0.1.0/biobuddy/components/real/rigidbody/contact_real.py +77 -0
  38. biobuddy-0.1.0/biobuddy/components/real/rigidbody/inertia_parameters_real.py +72 -0
  39. biobuddy-0.1.0/biobuddy/components/real/rigidbody/inertial_measurement_unit_real.py +100 -0
  40. biobuddy-0.1.0/biobuddy/components/real/rigidbody/marker_real.py +133 -0
  41. biobuddy-0.1.0/biobuddy/components/real/rigidbody/marker_weight.py +14 -0
  42. biobuddy-0.1.0/biobuddy/components/real/rigidbody/mesh_file_real.py +110 -0
  43. biobuddy-0.1.0/biobuddy/components/real/rigidbody/mesh_real.py +44 -0
  44. biobuddy-0.1.0/biobuddy/components/real/rigidbody/protocols.py +20 -0
  45. biobuddy-0.1.0/biobuddy/components/real/rigidbody/segment_coordinate_system_real.py +123 -0
  46. biobuddy-0.1.0/biobuddy/components/real/rigidbody/segment_real.py +277 -0
  47. biobuddy-0.1.0/biobuddy/components/real/rigidbody/segment_scaling.py +291 -0
  48. biobuddy-0.1.0/biobuddy/components/segment_utils.py +110 -0
  49. biobuddy-0.1.0/biobuddy/components/via_point_utils.py +37 -0
  50. biobuddy-0.1.0/biobuddy/mesh_parser/__init__.py +6 -0
  51. biobuddy-0.1.0/biobuddy/mesh_parser/mesh.py +27 -0
  52. biobuddy-0.1.0/biobuddy/mesh_parser/mesh_parser.py +77 -0
  53. biobuddy-0.1.0/biobuddy/mesh_parser/vtp_utils.py +173 -0
  54. biobuddy-0.1.0/biobuddy/model_modifiers/__init__.py +15 -0
  55. biobuddy-0.1.0/biobuddy/model_modifiers/joint_center_tool.py +1163 -0
  56. biobuddy-0.1.0/biobuddy/model_modifiers/merge_segments_tool.py +629 -0
  57. biobuddy-0.1.0/biobuddy/model_modifiers/modify_kinematic_chain_tool.py +538 -0
  58. biobuddy-0.1.0/biobuddy/model_modifiers/scale_tool.py +827 -0
  59. biobuddy-0.1.0/biobuddy/model_parser/__init__.py +12 -0
  60. biobuddy-0.1.0/biobuddy/model_parser/biorbd/__init__.py +7 -0
  61. biobuddy-0.1.0/biobuddy/model_parser/biorbd/biomod_configuration_parser.py +247 -0
  62. biobuddy-0.1.0/biobuddy/model_parser/biorbd/biomod_model_parser.py +397 -0
  63. biobuddy-0.1.0/biobuddy/model_parser/biorbd/utils.py +79 -0
  64. biobuddy-0.1.0/biobuddy/model_parser/opensim/__init__.py +7 -0
  65. biobuddy-0.1.0/biobuddy/model_parser/opensim/body.py +124 -0
  66. biobuddy-0.1.0/biobuddy/model_parser/opensim/coordinate.py +35 -0
  67. biobuddy-0.1.0/biobuddy/model_parser/opensim/functions.py +16 -0
  68. biobuddy-0.1.0/biobuddy/model_parser/opensim/joint.py +139 -0
  69. biobuddy-0.1.0/biobuddy/model_parser/opensim/marker.py +22 -0
  70. biobuddy-0.1.0/biobuddy/model_parser/opensim/muscle.py +181 -0
  71. biobuddy-0.1.0/biobuddy/model_parser/opensim/osim_configuration_parser.py +242 -0
  72. biobuddy-0.1.0/biobuddy/model_parser/opensim/osim_model_parser.py +851 -0
  73. biobuddy-0.1.0/biobuddy/model_parser/opensim/path_point.py +76 -0
  74. biobuddy-0.1.0/biobuddy/model_parser/opensim/spatial_transform.py +31 -0
  75. biobuddy-0.1.0/biobuddy/model_parser/opensim/utils.py +55 -0
  76. biobuddy-0.1.0/biobuddy/model_parser/protocol.py +21 -0
  77. biobuddy-0.1.0/biobuddy/model_writer/__init__.py +12 -0
  78. biobuddy-0.1.0/biobuddy/model_writer/biorbd/__init__.py +5 -0
  79. biobuddy-0.1.0/biobuddy/model_writer/biorbd/biorbd_model_writer.py +61 -0
  80. biobuddy-0.1.0/biobuddy/model_writer/opensim/__init__.py +5 -0
  81. biobuddy-0.1.0/biobuddy/model_writer/opensim/opensim_model_writer.py +27 -0
  82. biobuddy-0.1.0/biobuddy/model_writer/protocol.py +25 -0
  83. biobuddy-0.1.0/biobuddy/utils/__init__.py +18 -0
  84. biobuddy-0.1.0/biobuddy/utils/aliases.py +157 -0
  85. biobuddy-0.1.0/biobuddy/utils/c3d_data.py +141 -0
  86. biobuddy-0.1.0/biobuddy/utils/checks.py +8 -0
  87. biobuddy-0.1.0/biobuddy/utils/enums.py +44 -0
  88. biobuddy-0.1.0/biobuddy/utils/linear_algebra.py +673 -0
  89. biobuddy-0.1.0/biobuddy/utils/named_list.py +54 -0
  90. biobuddy-0.1.0/biobuddy/utils/protocols.py +60 -0
  91. biobuddy-0.1.0/biobuddy/version.py +1 -0
  92. biobuddy-0.1.0/biobuddy.egg-info/PKG-INFO +274 -0
  93. biobuddy-0.1.0/biobuddy.egg-info/SOURCES.txt +113 -0
  94. biobuddy-0.1.0/biobuddy.egg-info/dependency_links.txt +1 -0
  95. biobuddy-0.1.0/biobuddy.egg-info/requires.txt +12 -0
  96. biobuddy-0.1.0/biobuddy.egg-info/top_level.txt +1 -0
  97. biobuddy-0.1.0/pyproject.toml +57 -0
  98. biobuddy-0.1.0/setup.cfg +4 -0
  99. biobuddy-0.1.0/setup.py +35 -0
  100. biobuddy-0.1.0/tests/test_aliases.py +148 -0
  101. biobuddy-0.1.0/tests/test_biomechanical_model_real.py +283 -0
  102. biobuddy-0.1.0/tests/test_characteristics.py +803 -0
  103. biobuddy-0.1.0/tests/test_cleaning_vtp_files.py +33 -0
  104. biobuddy-0.1.0/tests/test_components_generic.py +1464 -0
  105. biobuddy-0.1.0/tests/test_components_real.py +1072 -0
  106. biobuddy-0.1.0/tests/test_functions.py +170 -0
  107. biobuddy-0.1.0/tests/test_joint_center_tool.py +834 -0
  108. biobuddy-0.1.0/tests/test_kinematic_chain_tool.py +263 -0
  109. biobuddy-0.1.0/tests/test_linear_algebra.py +827 -0
  110. biobuddy-0.1.0/tests/test_merge_tool.py +400 -0
  111. biobuddy-0.1.0/tests/test_model_creation.py +721 -0
  112. biobuddy-0.1.0/tests/test_model_dynamics.py +658 -0
  113. biobuddy-0.1.0/tests/test_osim_to_biomod.py +585 -0
  114. biobuddy-0.1.0/tests/test_scaling.py +985 -0
  115. biobuddy-0.1.0/tests/test_utils.py +545 -0
biobuddy-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 pyomeca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: biobuddy
3
+ Version: 0.1.0
4
+ Summary: A generic interface to generate a virtual buddy
5
+ Home-page: https://github.com/pyomeca/biobuddy
6
+ Author: Eve Charbonneau
7
+ Author-email: EveCharbie <eve.charbie@gmail.com>, Pariterre <pariterre@hotmail.com>
8
+ Project-URL: Documentation, https://github.com/pyomeca/biobuddy/tree/main#readme
9
+ Project-URL: Source, https://github.com/pyomeca/biobuddy
10
+ Project-URL: Tracker, https://github.com/pyomeca/biobuddy/issues
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy
17
+ Requires-Dist: ezc3d
18
+ Requires-Dist: lxml
19
+ Requires-Dist: scipy
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest; extra == "test"
22
+ Requires-Dist: black; extra == "test"
23
+ Requires-Dist: opensim; extra == "test"
24
+ Requires-Dist: biorbd; extra == "test"
25
+ Requires-Dist: pyorerun; extra == "test"
26
+ Requires-Dist: deepdiff; extra == "test"
27
+ Dynamic: author
28
+ Dynamic: home-page
29
+ Dynamic: license-file
30
+
31
+
32
+ ![biobuddy](https://github.com/user-attachments/assets/c8689155-0b26-4e13-835c-cdb6696e1acb)
33
+
34
+ `BioBuddy` is an open-source tool for [translating](#model-translation), [creating](#model-creation) and [personalizing](#model-personalization) musculoskeletal models across different formats (e.g., .osim, .bioMod). By enabling reliable interoperability between modeling environments, BioBuddy allows researchers to focus on scientific questions rather than technical constraints.
35
+
36
+ <!---
37
+ [![Actions Status](https://github.com/pyomeca/biobuddy/workflows/CI/badge.svg)](https://github.com/pyomeca/biobuddy/actions)
38
+ [![PyPI](https://anaconda.org/conda-forge/biobuddy/badges/latest_release_date.svg)](https://pypi.org/project/biobuddy/)
39
+ --->
40
+
41
+ [![codecov](https://codecov.io/gh/pyomeca/biobuddy/branch/main/graph/badge.svg)](https://codecov.io/gh/pyomeca/biobuddy)
42
+ [![Discord](https://img.shields.io/discord/1340640457327247460.svg?label=chat&logo=discord&color=7289DA)](https://discord.gg/Ux7BkdjQFW)
43
+
44
+ # How to install
45
+ Currently, the only way to install `BioBuddy` is from source. But it will be available on conda-forge and pip in the near future.
46
+
47
+ If you are a user, you can set up your environment with minimal dependencies.
48
+ ```bash
49
+ conda install -c conda-forge python=3.11.11 pip
50
+ pip install scipy==1.15.1 numpy==1.25.2 lxml ezc3d
51
+ ```
52
+ Note: On mac, you might need to add `conda install conda-forge::libcxx`
53
+
54
+ However, if you are a developer and want to contribute, you will need to set up your environment using the following command:
55
+ Due to the OpenSim dependency used only in BioBuddy's tests, we recommend using python=3.11.
56
+ ```bash
57
+ pip install pytest pytest-cov codecov
58
+ conda install -c opensim-org opensim=4.5.1
59
+ conda install -c conda-forge biorbd=1.11.2 deepdiff
60
+ ```
61
+
62
+ # Model translation
63
+ You can load the original model using one of the `BiomechanicalModelReal().from_[format]` methods, and then export it
64
+ into another format using the `BiomechanicalModelReal.to_[format]` method (see [example](examples/read_and_write_models.py)).
65
+ ```python3
66
+ from biobuddy import BiomechanicalModelReal
67
+
68
+ # Read an .osim file
69
+ model = BiomechanicalModelReal().from_osim(
70
+ filepath=osim_filepath,
71
+ # Other optional parameters here
72
+ )
73
+
74
+ # Translate it into a .bioMod file
75
+ model.to_biomod(biomod_filepath)
76
+ ```
77
+
78
+ # Model creation
79
+ A model can also be created from scratch using the `BiomechanicalModel`. In this generic model, everything can be defined
80
+ through functions (i.e., without numerical values). The subject specific model (a `BiomechanicalModelReal`) can then be
81
+ generated by evaluating the generic model with a motion capture trial. This feature is especially useful for kinematic
82
+ models where the joint centers are defined based on anatomical marker placement (like in the plug in gait, see full
83
+ [example](examples/create_model_from_c3d.py)).
84
+
85
+ Here is a simple example of how to add components to such a model:
86
+ ```python3
87
+
88
+ # Create the model
89
+ model = BiomechanicalModel()
90
+ de_leva = DeLevaTable(total_mass=60, sex=Sex.FEMALE)
91
+
92
+ # Add components to build the kinematic chain
93
+ model.add_segment(
94
+ Segment(
95
+ name="HEAD",
96
+ parent_name="TRUNK",
97
+ segment_coordinate_system=SegmentCoordinateSystem(
98
+ "BOTTOM_HEAD",
99
+ first_axis=Axis(name=Axis.Name.Z, start="BOTTOM_HEAD", end="HEAD_Z"),
100
+ second_axis=Axis(name=Axis.Name.X, start="BOTTOM_HEAD", end="HEAD_XZ"),
101
+ axis_to_keep=Axis.Name.Z,
102
+ ),
103
+ mesh=Mesh(("BOTTOM_HEAD", "TOP_HEAD", "HEAD_Z", "HEAD_XZ", "BOTTOM_HEAD")),
104
+ inertia_parameters=de_leva[SegmentName.HEAD],
105
+ )
106
+ )
107
+ model.segments["HEAD"].add_marker(Marker("BOTTOM_HEAD"))
108
+ model.segments["HEAD"].add_marker(Marker("TOP_HEAD"))
109
+ model.segments["HEAD"].add_marker(Marker("HEAD_Z"))
110
+ model.segments["HEAD"].add_marker(Marker("HEAD_XZ"))
111
+
112
+ # Evaluate the model with a motion capture trial
113
+ model_real = model.to_real(C3dData(c3d_filepath))
114
+ ```
115
+
116
+ There are many different components available to build a model (see this [example](examples/create_model.py) to see how to add those components to your model).
117
+
118
+ ![Build_segment](docs/images/Build_segment.png)
119
+
120
+ # Model personalization/modification
121
+ The current version of BioBuddy allows you to modify your `BiomechanicalModelReal` to personalize it to your subjects by:
122
+ - [Scaling](###scaling)
123
+ - [Identifying joint centers](###joint-center-identification)
124
+ - [Merging segments](#merging-segments)
125
+ - [Modifying the kinematic chain](#modifying-the-kinematic-chain)
126
+
127
+ ### Scaling:
128
+ The scaling is performed by the `ScaleTool` which can be initialized from scratch like this:
129
+ ```python3
130
+ original_model = BiomechanicalModelReal()
131
+
132
+ # Create the scaling configuration
133
+ scaling_configuration = ScaleTool(original_model)
134
+
135
+ # Add a scaling segment for the pelvis
136
+ scaling_configuration.add_scaling_segment(
137
+ SegmentScaling(
138
+ name="pelvis",
139
+ scaling_type= SegmentWiseScaling(
140
+ axis=Translations.XYZ,
141
+ marker_pairs=[
142
+ ["RASIS", "LASIS"],
143
+ ["RPSIS", "LPSIS"],
144
+ ]
145
+ )
146
+ )
147
+ )
148
+
149
+ # Add marker weights for the pelvis segment
150
+ scaling_configuration.add_marker_weight(MarkerWeight(name="RASIS", weight=1.0))
151
+ scaling_configuration.add_marker_weight(MarkerWeight(name="LASIS", weight=1.0))
152
+ scaling_configuration.add_marker_weight(MarkerWeight(name="RPSIS", weight=0.5))
153
+ scaling_configuration.add_marker_weight(MarkerWeight(name="LPSIS", weight=0.5))
154
+ ```
155
+ or by reading from an existing file (e.g., `.xml` or `.biomod`) using the appropriate `ScaleTool.from_[format]` method.
156
+ Once the scaling configuration is set up, you can scale your model based on a static trial (`.c3d` file) and the total
157
+ mass of your participant using the `ScaleTool.scale` like this:
158
+ ```python3
159
+ # Performing the scaling based on a static trial
160
+ scale_tool = ScaleTool.from_biomod(biomod_filepath)
161
+ scaled_model = scale_tool.scale(static_c3d=C3dData(filepath), mass=mass)
162
+ ```
163
+
164
+ For now, there are three scaling methods available:
165
+ - `BodyWiseScaling`: scales the entire body based on the total height of the participant.
166
+ - `SegmentWiseScaling`: scales the segment based on the distance between marker pairs.
167
+ - `AxisWiseScaling`: scales each axis of the segment independently based on the distance between marker pairs.
168
+ ![scaling_types](docs/images/Scaling_with_names.png)
169
+
170
+ ### Joint center identification:
171
+ The `JointCenterTool` allows you to identify the joint centers of your model based on the movement of segments during
172
+ functional trials.
173
+ First, you need to define the joint center configuration using `JointCenterTool.add()` method to define the joint
174
+ you want to modify.
175
+ Then, you can use the `JointCenterTool.replace_joint_centers()` method to modify each joint.
176
+ ```python3
177
+ from biobuddy import JointCenterTool
178
+
179
+ # Set up the joint center identification tool
180
+ joint_center_tool = JointCenterTool(scaled_model)
181
+ # Example for the right hip
182
+ joint_center_tool.add(
183
+ Score(
184
+ functional_c3d=C3dData(c3d_filepath, first_frame=100, last_frame=900),
185
+ parent_name="pelvis",
186
+ child_name="femur_r",
187
+ parent_marker_names=["RASIS", "LASIS", "LPSIS", "RPSIS"],
188
+ child_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg", "RMFE", "RLFE"],
189
+ )
190
+ )
191
+ # Example for the right knee
192
+ joint_center_tool.add(
193
+ Sara(
194
+ functional_c3d=C3dData(c3d_filepath),
195
+ parent_name="femur_r",
196
+ child_name="tibia_r",
197
+ parent_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg"],
198
+ child_marker_names=["RATT", "RUB_Tib", "RDF_Tib", "RDB_Tib", "RSPH", "RLM"],
199
+ )
200
+ )
201
+
202
+ # Perform the joint center identification
203
+ modified_model = joint_center_tool.replace_joint_centers()
204
+ ```
205
+ For now, two algorithms were implemented `SCoRE` to locate the position of the joint center and `SARA`
206
+ to identify the joint axis of rotation. Please note that in both cases, all segment components stay the same, only the
207
+ joint position and axis are modified.
208
+
209
+ ![SCoRE_SARA](docs/images/SCoRE_SARA.png)
210
+
211
+ ### Merging segments:
212
+ The `MergeSegmentTool` allows you to merge two segments into one (including inertial parameters and all the components on the segment).
213
+ You can define which segments you want to merge using `SegmentMerge`, which requires the names of the segments to merge and the name of the new segment.
214
+ ```python3
215
+ merge_tool = MergeSegmentsTool(original_model)
216
+ # By default, segments merge together setting the new segment coordinate as the mean of the old coordinates
217
+ merge_tool.add(
218
+ SegmentMerge(
219
+ name="LOWER_ARMS",
220
+ first_segment_name="L_LOWER_ARM",
221
+ second_segment_name="R_LOWER_ARM",
222
+ )
223
+ )
224
+ # But you can also merge one segment on top of another one by specifying the segment coordinate system to keep
225
+ merge_tool.add(
226
+ SegmentMerge(
227
+ name="R_LOWER_ARM_AND_HAND",
228
+ first_segment_name="R_LOWER_ARM",
229
+ second_segment_name="R_HAND",
230
+ merged_origin_name="R_LOWER_ARM",
231
+ )
232
+ )
233
+ modified_model = merge_tool.merge()
234
+ ```
235
+
236
+ ![merge_segments](docs/images/merge_segments.png)
237
+
238
+ ### Modifying the kinematic chain:
239
+ The `ModifyKinematicChainTool` allows you to modify the kinematic chain of your model.
240
+ For now, the only modification available is to change which segment is the first segment of the kinematic chain (`ChangeFirstSegment`).
241
+ It inverts all segments between the original first segment and the new first segment.
242
+ ```python3
243
+ kinematic_chain_modifier = ModifyKinematicChainTool(original_model)
244
+ kinematic_chain_modifier.add(ChangeFirstSegment(first_segment_name="FOOT"))
245
+ modified_model = kinematic_chain_modifier.modify()
246
+ ```
247
+
248
+ ![kinematic_chain_modifier](docs/images/kinematic_chain_modifier.png)
249
+
250
+ # Note
251
+ Understandably, not all modeling formats have the same functionalities, so some features may not be available for all
252
+ formats. We will try to keep here a list up to date of the features that are available in BioBuddy that are not
253
+ available for each format:
254
+ - biorbd (.bioMod):
255
+ - `PathPointCondition` is not implemented yet in biorbd. So if your BioBuddy model has this component, we recommend running `BiomechanicalModelReal.fix_via_points(q)` before `BiomechanicalModelReal.to_biomod(path)`. This will evaluate the via point conditions at the desired posture (which should be close to the range of motion during the movement studied). If the condition is not meet, the via point is inactive, so it is removed from the model. Please note that this is a destructive operation, once the conditions are evaluated, they are removed from the model and the remaining via points are fixed on the segments.
256
+ - `PathPointMovement` are not implemented yet in biorbd. So if your BioBuddy model has this components, we recommend running `BiomechanicalModelReal.fix_via_points(q)` before `BiomechanicalModelReal.to_biomod(path)`. This will fix the position of the moving via points, muscle origin, and muscle insertion by evaluating the position function at the desired posture (which should be close to the range of motion during the movement studied). Please note that this is a destructive operation, once the movements are evaluated, they are removed from the model and the remaining via points are fixed on the segments.
257
+
258
+ # How to cite
259
+ ```
260
+ @software{biobuddy_2025,
261
+ author = {Eve Charbonneau, Pierre Puchaud, Teddy Caderby, Mickael Begon, Amedeo Ceglia, Benjamin Michaud},
262
+ title = {Bringing the musculoskeletal modeling community together with BioBuddy},
263
+ month = april,
264
+ year = 2025,
265
+ publisher = {submitted to Congrès de la Société de biomécanique},
266
+ url = {https://github.com/pyomeca/biobuddy}
267
+ }
268
+ ```
269
+
270
+ # How to contribute
271
+ Our goal is to support as many musculoskeletal model formats as possible, so do not hesitate to contact us if you'd like to see your favorite format supported by BioBuddy.
272
+ If you are using BioBuddy and encounter any problem, please open an issue on this GitHub repository.
273
+ We are also open to suggestions for new features or improvements to existing functionality.
274
+ All contributions are welcome!
@@ -0,0 +1,244 @@
1
+
2
+ ![biobuddy](https://github.com/user-attachments/assets/c8689155-0b26-4e13-835c-cdb6696e1acb)
3
+
4
+ `BioBuddy` is an open-source tool for [translating](#model-translation), [creating](#model-creation) and [personalizing](#model-personalization) musculoskeletal models across different formats (e.g., .osim, .bioMod). By enabling reliable interoperability between modeling environments, BioBuddy allows researchers to focus on scientific questions rather than technical constraints.
5
+
6
+ <!---
7
+ [![Actions Status](https://github.com/pyomeca/biobuddy/workflows/CI/badge.svg)](https://github.com/pyomeca/biobuddy/actions)
8
+ [![PyPI](https://anaconda.org/conda-forge/biobuddy/badges/latest_release_date.svg)](https://pypi.org/project/biobuddy/)
9
+ --->
10
+
11
+ [![codecov](https://codecov.io/gh/pyomeca/biobuddy/branch/main/graph/badge.svg)](https://codecov.io/gh/pyomeca/biobuddy)
12
+ [![Discord](https://img.shields.io/discord/1340640457327247460.svg?label=chat&logo=discord&color=7289DA)](https://discord.gg/Ux7BkdjQFW)
13
+
14
+ # How to install
15
+ Currently, the only way to install `BioBuddy` is from source. But it will be available on conda-forge and pip in the near future.
16
+
17
+ If you are a user, you can set up your environment with minimal dependencies.
18
+ ```bash
19
+ conda install -c conda-forge python=3.11.11 pip
20
+ pip install scipy==1.15.1 numpy==1.25.2 lxml ezc3d
21
+ ```
22
+ Note: On mac, you might need to add `conda install conda-forge::libcxx`
23
+
24
+ However, if you are a developer and want to contribute, you will need to set up your environment using the following command:
25
+ Due to the OpenSim dependency used only in BioBuddy's tests, we recommend using python=3.11.
26
+ ```bash
27
+ pip install pytest pytest-cov codecov
28
+ conda install -c opensim-org opensim=4.5.1
29
+ conda install -c conda-forge biorbd=1.11.2 deepdiff
30
+ ```
31
+
32
+ # Model translation
33
+ You can load the original model using one of the `BiomechanicalModelReal().from_[format]` methods, and then export it
34
+ into another format using the `BiomechanicalModelReal.to_[format]` method (see [example](examples/read_and_write_models.py)).
35
+ ```python3
36
+ from biobuddy import BiomechanicalModelReal
37
+
38
+ # Read an .osim file
39
+ model = BiomechanicalModelReal().from_osim(
40
+ filepath=osim_filepath,
41
+ # Other optional parameters here
42
+ )
43
+
44
+ # Translate it into a .bioMod file
45
+ model.to_biomod(biomod_filepath)
46
+ ```
47
+
48
+ # Model creation
49
+ A model can also be created from scratch using the `BiomechanicalModel`. In this generic model, everything can be defined
50
+ through functions (i.e., without numerical values). The subject specific model (a `BiomechanicalModelReal`) can then be
51
+ generated by evaluating the generic model with a motion capture trial. This feature is especially useful for kinematic
52
+ models where the joint centers are defined based on anatomical marker placement (like in the plug in gait, see full
53
+ [example](examples/create_model_from_c3d.py)).
54
+
55
+ Here is a simple example of how to add components to such a model:
56
+ ```python3
57
+
58
+ # Create the model
59
+ model = BiomechanicalModel()
60
+ de_leva = DeLevaTable(total_mass=60, sex=Sex.FEMALE)
61
+
62
+ # Add components to build the kinematic chain
63
+ model.add_segment(
64
+ Segment(
65
+ name="HEAD",
66
+ parent_name="TRUNK",
67
+ segment_coordinate_system=SegmentCoordinateSystem(
68
+ "BOTTOM_HEAD",
69
+ first_axis=Axis(name=Axis.Name.Z, start="BOTTOM_HEAD", end="HEAD_Z"),
70
+ second_axis=Axis(name=Axis.Name.X, start="BOTTOM_HEAD", end="HEAD_XZ"),
71
+ axis_to_keep=Axis.Name.Z,
72
+ ),
73
+ mesh=Mesh(("BOTTOM_HEAD", "TOP_HEAD", "HEAD_Z", "HEAD_XZ", "BOTTOM_HEAD")),
74
+ inertia_parameters=de_leva[SegmentName.HEAD],
75
+ )
76
+ )
77
+ model.segments["HEAD"].add_marker(Marker("BOTTOM_HEAD"))
78
+ model.segments["HEAD"].add_marker(Marker("TOP_HEAD"))
79
+ model.segments["HEAD"].add_marker(Marker("HEAD_Z"))
80
+ model.segments["HEAD"].add_marker(Marker("HEAD_XZ"))
81
+
82
+ # Evaluate the model with a motion capture trial
83
+ model_real = model.to_real(C3dData(c3d_filepath))
84
+ ```
85
+
86
+ There are many different components available to build a model (see this [example](examples/create_model.py) to see how to add those components to your model).
87
+
88
+ ![Build_segment](docs/images/Build_segment.png)
89
+
90
+ # Model personalization/modification
91
+ The current version of BioBuddy allows you to modify your `BiomechanicalModelReal` to personalize it to your subjects by:
92
+ - [Scaling](###scaling)
93
+ - [Identifying joint centers](###joint-center-identification)
94
+ - [Merging segments](#merging-segments)
95
+ - [Modifying the kinematic chain](#modifying-the-kinematic-chain)
96
+
97
+ ### Scaling:
98
+ The scaling is performed by the `ScaleTool` which can be initialized from scratch like this:
99
+ ```python3
100
+ original_model = BiomechanicalModelReal()
101
+
102
+ # Create the scaling configuration
103
+ scaling_configuration = ScaleTool(original_model)
104
+
105
+ # Add a scaling segment for the pelvis
106
+ scaling_configuration.add_scaling_segment(
107
+ SegmentScaling(
108
+ name="pelvis",
109
+ scaling_type= SegmentWiseScaling(
110
+ axis=Translations.XYZ,
111
+ marker_pairs=[
112
+ ["RASIS", "LASIS"],
113
+ ["RPSIS", "LPSIS"],
114
+ ]
115
+ )
116
+ )
117
+ )
118
+
119
+ # Add marker weights for the pelvis segment
120
+ scaling_configuration.add_marker_weight(MarkerWeight(name="RASIS", weight=1.0))
121
+ scaling_configuration.add_marker_weight(MarkerWeight(name="LASIS", weight=1.0))
122
+ scaling_configuration.add_marker_weight(MarkerWeight(name="RPSIS", weight=0.5))
123
+ scaling_configuration.add_marker_weight(MarkerWeight(name="LPSIS", weight=0.5))
124
+ ```
125
+ or by reading from an existing file (e.g., `.xml` or `.biomod`) using the appropriate `ScaleTool.from_[format]` method.
126
+ Once the scaling configuration is set up, you can scale your model based on a static trial (`.c3d` file) and the total
127
+ mass of your participant using the `ScaleTool.scale` like this:
128
+ ```python3
129
+ # Performing the scaling based on a static trial
130
+ scale_tool = ScaleTool.from_biomod(biomod_filepath)
131
+ scaled_model = scale_tool.scale(static_c3d=C3dData(filepath), mass=mass)
132
+ ```
133
+
134
+ For now, there are three scaling methods available:
135
+ - `BodyWiseScaling`: scales the entire body based on the total height of the participant.
136
+ - `SegmentWiseScaling`: scales the segment based on the distance between marker pairs.
137
+ - `AxisWiseScaling`: scales each axis of the segment independently based on the distance between marker pairs.
138
+ ![scaling_types](docs/images/Scaling_with_names.png)
139
+
140
+ ### Joint center identification:
141
+ The `JointCenterTool` allows you to identify the joint centers of your model based on the movement of segments during
142
+ functional trials.
143
+ First, you need to define the joint center configuration using `JointCenterTool.add()` method to define the joint
144
+ you want to modify.
145
+ Then, you can use the `JointCenterTool.replace_joint_centers()` method to modify each joint.
146
+ ```python3
147
+ from biobuddy import JointCenterTool
148
+
149
+ # Set up the joint center identification tool
150
+ joint_center_tool = JointCenterTool(scaled_model)
151
+ # Example for the right hip
152
+ joint_center_tool.add(
153
+ Score(
154
+ functional_c3d=C3dData(c3d_filepath, first_frame=100, last_frame=900),
155
+ parent_name="pelvis",
156
+ child_name="femur_r",
157
+ parent_marker_names=["RASIS", "LASIS", "LPSIS", "RPSIS"],
158
+ child_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg", "RMFE", "RLFE"],
159
+ )
160
+ )
161
+ # Example for the right knee
162
+ joint_center_tool.add(
163
+ Sara(
164
+ functional_c3d=C3dData(c3d_filepath),
165
+ parent_name="femur_r",
166
+ child_name="tibia_r",
167
+ parent_marker_names=["RGT", "RUB_Leg", "RUF_Leg", "FBF_Leg"],
168
+ child_marker_names=["RATT", "RUB_Tib", "RDF_Tib", "RDB_Tib", "RSPH", "RLM"],
169
+ )
170
+ )
171
+
172
+ # Perform the joint center identification
173
+ modified_model = joint_center_tool.replace_joint_centers()
174
+ ```
175
+ For now, two algorithms were implemented `SCoRE` to locate the position of the joint center and `SARA`
176
+ to identify the joint axis of rotation. Please note that in both cases, all segment components stay the same, only the
177
+ joint position and axis are modified.
178
+
179
+ ![SCoRE_SARA](docs/images/SCoRE_SARA.png)
180
+
181
+ ### Merging segments:
182
+ The `MergeSegmentTool` allows you to merge two segments into one (including inertial parameters and all the components on the segment).
183
+ You can define which segments you want to merge using `SegmentMerge`, which requires the names of the segments to merge and the name of the new segment.
184
+ ```python3
185
+ merge_tool = MergeSegmentsTool(original_model)
186
+ # By default, segments merge together setting the new segment coordinate as the mean of the old coordinates
187
+ merge_tool.add(
188
+ SegmentMerge(
189
+ name="LOWER_ARMS",
190
+ first_segment_name="L_LOWER_ARM",
191
+ second_segment_name="R_LOWER_ARM",
192
+ )
193
+ )
194
+ # But you can also merge one segment on top of another one by specifying the segment coordinate system to keep
195
+ merge_tool.add(
196
+ SegmentMerge(
197
+ name="R_LOWER_ARM_AND_HAND",
198
+ first_segment_name="R_LOWER_ARM",
199
+ second_segment_name="R_HAND",
200
+ merged_origin_name="R_LOWER_ARM",
201
+ )
202
+ )
203
+ modified_model = merge_tool.merge()
204
+ ```
205
+
206
+ ![merge_segments](docs/images/merge_segments.png)
207
+
208
+ ### Modifying the kinematic chain:
209
+ The `ModifyKinematicChainTool` allows you to modify the kinematic chain of your model.
210
+ For now, the only modification available is to change which segment is the first segment of the kinematic chain (`ChangeFirstSegment`).
211
+ It inverts all segments between the original first segment and the new first segment.
212
+ ```python3
213
+ kinematic_chain_modifier = ModifyKinematicChainTool(original_model)
214
+ kinematic_chain_modifier.add(ChangeFirstSegment(first_segment_name="FOOT"))
215
+ modified_model = kinematic_chain_modifier.modify()
216
+ ```
217
+
218
+ ![kinematic_chain_modifier](docs/images/kinematic_chain_modifier.png)
219
+
220
+ # Note
221
+ Understandably, not all modeling formats have the same functionalities, so some features may not be available for all
222
+ formats. We will try to keep here a list up to date of the features that are available in BioBuddy that are not
223
+ available for each format:
224
+ - biorbd (.bioMod):
225
+ - `PathPointCondition` is not implemented yet in biorbd. So if your BioBuddy model has this component, we recommend running `BiomechanicalModelReal.fix_via_points(q)` before `BiomechanicalModelReal.to_biomod(path)`. This will evaluate the via point conditions at the desired posture (which should be close to the range of motion during the movement studied). If the condition is not meet, the via point is inactive, so it is removed from the model. Please note that this is a destructive operation, once the conditions are evaluated, they are removed from the model and the remaining via points are fixed on the segments.
226
+ - `PathPointMovement` are not implemented yet in biorbd. So if your BioBuddy model has this components, we recommend running `BiomechanicalModelReal.fix_via_points(q)` before `BiomechanicalModelReal.to_biomod(path)`. This will fix the position of the moving via points, muscle origin, and muscle insertion by evaluating the position function at the desired posture (which should be close to the range of motion during the movement studied). Please note that this is a destructive operation, once the movements are evaluated, they are removed from the model and the remaining via points are fixed on the segments.
227
+
228
+ # How to cite
229
+ ```
230
+ @software{biobuddy_2025,
231
+ author = {Eve Charbonneau, Pierre Puchaud, Teddy Caderby, Mickael Begon, Amedeo Ceglia, Benjamin Michaud},
232
+ title = {Bringing the musculoskeletal modeling community together with BioBuddy},
233
+ month = april,
234
+ year = 2025,
235
+ publisher = {submitted to Congrès de la Société de biomécanique},
236
+ url = {https://github.com/pyomeca/biobuddy}
237
+ }
238
+ ```
239
+
240
+ # How to contribute
241
+ Our goal is to support as many musculoskeletal model formats as possible, so do not hesitate to contact us if you'd like to see your favorite format supported by BioBuddy.
242
+ If you are using BioBuddy and encounter any problem, please open an issue on this GitHub repository.
243
+ We are also open to suggestions for new features or improvements to existing functionality.
244
+ All contributions are welcome!
@@ -0,0 +1,31 @@
1
+ # Version
2
+ from .version import __version__
3
+
4
+ # Some classes to define the BiomechanicalModel
5
+ from .components import *
6
+
7
+ # Some utilities
8
+ from .utils import *
9
+
10
+ # Segment predefined characteristics
11
+ from .characteristics import *
12
+
13
+ # Mesh modifications
14
+ from .mesh_parser import *
15
+
16
+ # Model parsers
17
+ from .model_parser import *
18
+
19
+ # Model modifiers
20
+ from .model_modifiers import *
21
+
22
+
23
+ __all__ = (
24
+ components.__all__
25
+ + utils.__all__
26
+ + characteristics.__all__
27
+ + mesh_parser.__all__
28
+ + model_parser.__all__
29
+ + model_modifiers.__all__
30
+ + ["__version__"]
31
+ )
@@ -0,0 +1,8 @@
1
+ from .de_leva import DeLevaTable, Sex, SegmentName
2
+
3
+
4
+ __all__ = [
5
+ DeLevaTable.__name__,
6
+ Sex.__name__,
7
+ SegmentName.__name__,
8
+ ]