From 056c416c31013211dfb4ac42edfb31931ce74039 Mon Sep 17 00:00:00 2001 From: Charbie Date: Thu, 9 Jan 2025 17:15:34 +0400 Subject: [PATCH 01/10] added rangesQ and rangesQdot --- binding/python3/model_creation/__init__.py | 1 + .../model_creation/biomechanical_model.py | 2 ++ .../python3/model_creation/range_of_motion.py | 33 +++++++++++++++++++ binding/python3/model_creation/segment.py | 21 ++++++++++++ .../python3/model_creation/segment_real.py | 9 +++++ 5 files changed, 66 insertions(+) create mode 100644 binding/python3/model_creation/range_of_motion.py diff --git a/binding/python3/model_creation/__init__.py b/binding/python3/model_creation/__init__.py index a484794f0..215b5f4cb 100644 --- a/binding/python3/model_creation/__init__.py +++ b/binding/python3/model_creation/__init__.py @@ -8,6 +8,7 @@ from .mesh import Mesh from .protocols import Data, GenericDynamicModel from .rotations import Rotations +from .range_of_motion import RangeOfMotion, Ranges from .segment import Segment from .segment_coordinate_system import SegmentCoordinateSystem from .translations import Translations diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 526071946..3844e28d5 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -60,6 +60,8 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: segment_coordinate_system=scs, translations=s.translations, rotations=s.rotations, + q_ranges=s.q_ranges, + qdot_ranges=s.qdot_ranges, inertia_parameters=inertia_parameters, mesh=mesh, ) diff --git a/binding/python3/model_creation/range_of_motion.py b/binding/python3/model_creation/range_of_motion.py new file mode 100644 index 000000000..3d7503882 --- /dev/null +++ b/binding/python3/model_creation/range_of_motion.py @@ -0,0 +1,33 @@ + +from enum import Enum + + +class Ranges(Enum): + Q = "Q" + Qdot = "Qdot" + + +class RangeOfMotion: + def __init__(self, + type: Ranges, + min_bound: list[float], + max_bound: list[float]): + + self.type = type + self.min_bound = min_bound + self.max_bound = max_bound + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + if self.type == Ranges.Q: + out_string = f"\trangesQ \n" + elif self.type == Ranges.Qdot: + out_string = f"\trangesQdot \n" + else: + raise RuntimeError("RangeOfMotion's type must be Range.Q or Ranges.Qdot") + + for i_dof in range(len(self.min_bound)): + out_string += f"\t\t{self.min_bound[i_dof]:0.5f}\t{self.max_bound[i_dof]:0.5f}\n" + out_string += "\n" + + return out_string \ No newline at end of file diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 7b97e92ff..3d0108a5a 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -2,6 +2,7 @@ from .marker import Marker from .mesh import Mesh from .rotations import Rotations +from .range_of_motion import RangeOfMotion, Ranges from .segment_coordinate_system import SegmentCoordinateSystem from .translations import Translations @@ -13,6 +14,8 @@ def __init__( parent_name: str = "", translations: Translations = Translations.NONE, rotations: Rotations = Rotations.NONE, + q_ranges: RangeOfMotion = None, + qdot_ranges: RangeOfMotion = None, segment_coordinate_system: SegmentCoordinateSystem = None, inertia_parameters: InertiaParameters = None, mesh: Mesh = None, @@ -40,6 +43,8 @@ def __init__( self.parent_name = parent_name self.translations = translations self.rotations = rotations + self.q_ranges = q_ranges + self.qdot_ranges = qdot_ranges self.markers = [] self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters @@ -61,3 +66,19 @@ def add_marker(self, marker: Marker): marker.parent_name = self.name self.markers.append(marker) + + def add_range(self, type: Ranges, min_bound, max_bound): + """ + Add a new rangeQ to the segment + + Parameters + ---------- + marker + The marker to add + """ + if type == Ranges.Q: + self.q_ranges = RangeOfMotion(type=type, min_bound=min_bound, max_bound=max_bound) + elif type == Ranges.Qdot: + self.qdot_ranges = RangeOfMotion(type=type, min_bound=min_bound, max_bound=max_bound) + else: + raise RuntimeError(f"add_range's type must be Ranges.Q or Ranges.Qdot (you have {type})") \ No newline at end of file diff --git a/binding/python3/model_creation/segment_real.py b/binding/python3/model_creation/segment_real.py index def007758..30636d767 100644 --- a/binding/python3/model_creation/segment_real.py +++ b/binding/python3/model_creation/segment_real.py @@ -2,6 +2,7 @@ from .marker_real import MarkerReal from .mesh_real import MeshReal from .rotations import Rotations +from .range_of_motion import RangeOfMotion from .segment_coordinate_system_real import SegmentCoordinateSystemReal from .translations import Translations @@ -14,6 +15,8 @@ def __init__( segment_coordinate_system: SegmentCoordinateSystemReal = None, translations: Translations = Translations.NONE, rotations: Rotations = Rotations.NONE, + q_ranges: RangeOfMotion = None, + qdot_ranges: RangeOfMotion = None, inertia_parameters: InertiaParametersReal = None, mesh: MeshReal = None, ): @@ -21,6 +24,8 @@ def __init__( self.parent_name = parent_name self.translations = translations self.rotations = rotations + self.q_ranges = q_ranges + self.qdot_ranges = qdot_ranges self.markers = [] self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters @@ -40,6 +45,10 @@ def __str__(self): out_string += f"\ttranslations {self.translations.value}\n" if self.rotations != Rotations.NONE: out_string += f"\trotations {self.rotations.value}\n" + if self.q_ranges != None: + out_string += str(self.q_ranges) + if self.qdot_ranges != None: + out_string += str(self.qdot_ranges) if self.inertia_parameters: out_string += str(self.inertia_parameters) if self.mesh: From 81b176f03c381ddd60f8100477e7fb1e9157c0b9 Mon Sep 17 00:00:00 2001 From: Charbie Date: Fri, 10 Jan 2025 13:50:08 +0400 Subject: [PATCH 02/10] added contacts --- binding/python3/model_creation/__init__.py | 2 + .../model_creation/biomechanical_model.py | 3 + binding/python3/model_creation/contact.py | 43 +++++++++ .../python3/model_creation/contact_real.py | 93 +++++++++++++++++++ binding/python3/model_creation/segment.py | 20 ++++ .../python3/model_creation/segment_real.py | 14 +++ 6 files changed, 175 insertions(+) create mode 100644 binding/python3/model_creation/contact.py create mode 100644 binding/python3/model_creation/contact_real.py diff --git a/binding/python3/model_creation/__init__.py b/binding/python3/model_creation/__init__.py index 215b5f4cb..abd16d1b7 100644 --- a/binding/python3/model_creation/__init__.py +++ b/binding/python3/model_creation/__init__.py @@ -5,6 +5,7 @@ from .axis import Axis from .inertia_parameters import InertiaParameters from .marker import Marker +from .contact import Contact from .mesh import Mesh from .protocols import Data, GenericDynamicModel from .rotations import Rotations @@ -17,6 +18,7 @@ from .biomechanical_model_real import BiomechanicalModelReal from .axis_real import AxisReal from .marker_real import MarkerReal +from .contact_real import ContactReal from .mesh_real import MeshReal from .segment_real import SegmentReal from .segment_coordinate_system_real import SegmentCoordinateSystemReal diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 3844e28d5..1287e73c9 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -69,6 +69,9 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: for marker in s.markers: model.segments[name].add_marker(marker.to_marker(data, model, scs)) + for contact in s.contacts: + model.segments[name].add_contact(contact.to_contact(data)) + return model def write(self, save_path: str, data: Data): diff --git a/binding/python3/model_creation/contact.py b/binding/python3/model_creation/contact.py new file mode 100644 index 000000000..ff16dfabd --- /dev/null +++ b/binding/python3/model_creation/contact.py @@ -0,0 +1,43 @@ +from typing import Callable + +from .protocols import Data +from .translations import Translations +from .contact_real import ContactReal + +class Contact: + def __init__( + self, + name: str, + function: Callable | str = None, + parent_name: str = None, + axis: Translations = None, + ): + """ + Parameters + ---------- + name + The name of the new contact + function + The function (f(m) -> np.ndarray, where m is a dict of markers) that defines the contact with. + parent_name + The name of the parent the contact is attached to + axis + The axis of the contact + """ + self.name = name + function = function if function is not None else self.name + self.function = (lambda m, bio: m[function]) if isinstance(function, str) else function + self.parent_name = parent_name + self.axis = axis + + + def to_contact( + self, data: Data + ) -> ContactReal: + return ContactReal.from_data( + data, + self.name, + self.function, + self.parent_name, + self.axis, + ) \ No newline at end of file diff --git a/binding/python3/model_creation/contact_real.py b/binding/python3/model_creation/contact_real.py new file mode 100644 index 000000000..11964fdc1 --- /dev/null +++ b/binding/python3/model_creation/contact_real.py @@ -0,0 +1,93 @@ +from typing import Callable + +import numpy as np + +from .protocols import Data +from .translations import Translations + +class ContactReal: + def __init__( + self, + name: str, + parent_name: str, + position: tuple[int | float, int | float, int | float] | np.ndarray = None, + axis: Translations = None, + ): + """ + Parameters + ---------- + name + The name of the new contact + parent_name + The name of the parent the contact is attached to + position + The 3d position of the contact + axis + The axis of the contact + """ + self.name = name + self.parent_name = parent_name + if position is None: + position = np.array((0, 0, 0, 1)) + self.position = position if isinstance(position, np.ndarray) else np.array(position) + self.axis = axis + + # @property + # def mean_position(self) -> np.ndarray: + # """ + # Get the mean value (over time) of the marker position + # """ + # p = np.array(self.position) + # p = p if len(p.shape) == 1 else np.nanmean(p, axis=1) + # p = p if len(p.shape) == 1 else np.nanmean(p, axis=0) + # return p + + @staticmethod + def from_data( + data: Data, + name: str, + function: Callable, + parent_name: str, + axis: Translations = None, + ): + """ + This is a constructor for the Contact class. It evaluates the function that defines the contact to get an + actual position + + Parameters + ---------- + data + The data to pick the data from + name + The name of the new contact + function + The function (f(m) -> np.ndarray, where m is a dict of markers (XYZ1 x time)) that defines the contacts in the local joint coordinates. + parent_name + The name of the parent the contact is attached to + axis + The axis of the contact + """ + + # TODO: Charbie + + # Get the position of the contact points and do some sanity checks + p: np.ndarray = function(data.values) + if not isinstance(p, np.ndarray): + raise RuntimeError(f"The function {function} must return a np.ndarray of dimension 3xT (XYZ x time)") + if len(p.shape) == 1: + p = p[:, np.newaxis] + + if len(p.shape) != 2 or p.shape[0] != 3: + raise RuntimeError(f"The function {function} must return a np.ndarray of dimension 3xT (XYZ x time)") + + return ContactReal(name, parent_name, p, axis) + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + out_string = f"contact {self.name}\n" + out_string += f"\tparent {self.parent_name}\n" + + out_string += f"\tposition {self.position[0]:0.4f} {self.position[1]:0.4f} {self.position[2]:0.4f}\n" + out_string += f"\taxis {self.axis.value}\n" + out_string += "endcontact\n" + return out_string diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 3d0108a5a..66a331a7a 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -1,5 +1,6 @@ from .inertia_parameters import InertiaParameters from .marker import Marker +from .contact import Contact from .mesh import Mesh from .rotations import Rotations from .range_of_motion import RangeOfMotion, Ranges @@ -46,6 +47,7 @@ def __init__( self.q_ranges = q_ranges self.qdot_ranges = qdot_ranges self.markers = [] + self.contacts = [] self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters self.mesh = mesh @@ -67,6 +69,24 @@ def add_marker(self, marker: Marker): marker.parent_name = self.name self.markers.append(marker) + def add_contact(self, contact: Contact): + """ + Add a new contact to the segment + + Parameters + ---------- + contact + The contact to add + """ + if contact.parent_name is None: + raise ValueError(f"Contacts must have parents. Contact {contact.name} does not have a parent.") + elif contact.parent_name != self.name: + raise ValueError( + "The contact name should be the same as the 'key'." + ) + contact.parent_name = self.name + self.contacts.append(contact) + def add_range(self, type: Ranges, min_bound, max_bound): """ Add a new rangeQ to the segment diff --git a/binding/python3/model_creation/segment_real.py b/binding/python3/model_creation/segment_real.py index 30636d767..fca3e7622 100644 --- a/binding/python3/model_creation/segment_real.py +++ b/binding/python3/model_creation/segment_real.py @@ -1,5 +1,6 @@ from .inertia_parameters_real import InertiaParametersReal from .marker_real import MarkerReal +from .contact import Contact from .mesh_real import MeshReal from .rotations import Rotations from .range_of_motion import RangeOfMotion @@ -27,6 +28,7 @@ def __init__( self.q_ranges = q_ranges self.qdot_ranges = qdot_ranges self.markers = [] + self.contacts = [] self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters self.mesh = mesh @@ -34,6 +36,11 @@ def __init__( def add_marker(self, marker: MarkerReal): self.markers.append(marker) + def add_contact(self, contact: Contact): + if contact.parent_name is None: + raise RuntimeError(f"Contacts must have parents. Contact {contact.name} does not have a parent.") + self.contacts.append(contact) + def __str__(self): # Define the print function, so it automatically formats things in the file properly out_string = f"segment {self.name}\n" @@ -60,4 +67,11 @@ def __str__(self): for marker in self.markers: marker.parent_name = marker.parent_name if marker.parent_name is not None else self.name out_string += str(marker) + + # Also print the contacts attached to the segment + if self.contacts: + for contact in self.contacts: + contact.parent_name = contact.parent_name + out_string += str(contact) + return out_string From fe4405f5d9fd1ce1b491475e2fc73db79c83d4c7 Mon Sep 17 00:00:00 2001 From: Charbie Date: Mon, 13 Jan 2025 17:27:14 +0400 Subject: [PATCH 03/10] EOD: added muscles, but did not test yet --- binding/python3/model_creation/__init__.py | 3 + .../model_creation/biomechanical_model.py | 45 +++-- .../biomechanical_model_real.py | 27 ++- .../python3/model_creation/contact_real.py | 13 -- binding/python3/model_creation/muscle.py | 61 ++++++ .../python3/model_creation/muscle_group.py | 32 +++ binding/python3/model_creation/muscle_real.py | 187 ++++++++++++++++++ binding/python3/model_creation/segment.py | 2 +- 8 files changed, 337 insertions(+), 33 deletions(-) create mode 100644 binding/python3/model_creation/muscle.py create mode 100644 binding/python3/model_creation/muscle_group.py create mode 100644 binding/python3/model_creation/muscle_real.py diff --git a/binding/python3/model_creation/__init__.py b/binding/python3/model_creation/__init__.py index abd16d1b7..b0d4001f1 100644 --- a/binding/python3/model_creation/__init__.py +++ b/binding/python3/model_creation/__init__.py @@ -6,6 +6,8 @@ from .inertia_parameters import InertiaParameters from .marker import Marker from .contact import Contact +from .muscle import Muscle +from .muscle_group import MuscleGroup from .mesh import Mesh from .protocols import Data, GenericDynamicModel from .rotations import Rotations @@ -19,6 +21,7 @@ from .axis_real import AxisReal from .marker_real import MarkerReal from .contact_real import ContactReal +from .muscle_real import MuscleReal, MuscleType, MuscleStateType from .mesh_real import MeshReal from .segment_real import SegmentReal from .segment_coordinate_system_real import SegmentCoordinateSystemReal diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 1287e73c9..596475577 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -1,6 +1,7 @@ from .protocols import Data from .segment_real import SegmentReal -from .segment import Segment +from .muscle_group import MuscleGroup +from .muscle_real import MuscleReal from .segment_coordinate_system_real import SegmentCoordinateSystemReal from .biomechanical_model_real import BiomechanicalModelReal @@ -8,22 +9,13 @@ class BiomechanicalModel: def __init__(self, bio_sym_path: str = None): self.segments = {} + self.muscle_groups = {} + self.muscles = {} if bio_sym_path is None: return raise NotImplementedError("bioMod files are not readable yet") - def __getitem__(self, name: str): - return self.segments[name] - - def __setitem__(self, name: str, segment: Segment): - if segment.name is not None and segment.name != name: - raise ValueError( - "The segment name should be the same as the 'key'. Alternatively, segment.name can be left undefined" - ) - segment.name = name # Make sure the name of the segment fits the internal one - self.segments[name] = segment - def to_real(self, data: Data) -> BiomechanicalModelReal: """ Collapse the model to an actual personalized biomechanical model based on the generic model and the data @@ -54,7 +46,7 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: if s.mesh is not None: mesh = s.mesh.to_mesh(data, model, scs) - model[s.name] = SegmentReal( + model.segments[s.name] = SegmentReal( name=s.name, parent_name=s.parent_name, segment_coordinate_system=scs, @@ -72,6 +64,33 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: for contact in s.contacts: model.segments[name].add_contact(contact.to_contact(data)) + for name in self.muscle_groups: + mg = self.muscle_groups[name] + + model.muscle_groups[mg.name] = MuscleGroup( + name=mg.name, + origin_parent_name=mg.origin_parent_name, + insertion_parent_name=mg.insertion_parent_name, + ) + + for name in self.muscles: + m = self.muscles[name] + + if m.muscle_group not in model.muscle_groups: + raise RuntimeError(f"Please create the muscle group {m.muscle_group} before putting the muscle {m.name} in it.") + + model.muscles[m.name] = MuscleReal( + name=m.name, + type=m.type, + state_type=m.state_type, + muscle_group=m.muscle_group, + origin_position=m.origin_position, + insertion_position=m.insertion_position, + optimal_length=m.optimal_length, + maximal_force=m.maximal_force, + tendon_slack_length=m.tendon_slack_length, + penation_angle=m.penation_angle) + return model def write(self, save_path: str, data: Data): diff --git a/binding/python3/model_creation/biomechanical_model_real.py b/binding/python3/model_creation/biomechanical_model_real.py index a2148fa9f..ad7407d08 100644 --- a/binding/python3/model_creation/biomechanical_model_real.py +++ b/binding/python3/model_creation/biomechanical_model_real.py @@ -1,23 +1,38 @@ class BiomechanicalModelReal: def __init__(self): from .segment_real import SegmentReal # Imported here to prevent from circular imports + from .muscle_group import MuscleGroup + from .muscle_real import MuscleReal self.segments: dict[str:SegmentReal, ...] = {} # From Pythom 3.7 the insertion order in a dict is preserved. This is important because when writing a new # .bioMod file, the order of the segment matters + self.muscle_groups: dict[str:MuscleGroup, ...] = {} + self.muscles: dict[str:MuscleReal, ...] = {} - def __getitem__(self, name: str): - return self.segments[name] - - def __setitem__(self, name: str, segment: "SegmentReal"): - segment.name = name # Make sure the name of the segment fits the internal one - self.segments[name] = segment def __str__(self): out_string = "version 4\n\n" + + out_string += "// --------------------------------------------------------------" + out_string += "// SEGMENTS" + out_string += "// --------------------------------------------------------------" for name in self.segments: out_string += str(self.segments[name]) out_string += "\n\n\n" # Give some space between segments + + out_string += "// --------------------------------------------------------------" + out_string += "// MUSCLE GROUPS" + out_string += "// --------------------------------------------------------------" + for name in self.muscle_groups: + out_string += str(self.muscle_groups[name]) + + out_string += "// --------------------------------------------------------------" + out_string += "// MUSCLES" + out_string += "// --------------------------------------------------------------" + for name in self.muscles: + out_string += str(self.muscles[name]) + out_string += "\n\n\n" # Give some space between muscles return out_string def write(self, file_path: str): diff --git a/binding/python3/model_creation/contact_real.py b/binding/python3/model_creation/contact_real.py index 11964fdc1..7c9c1e2c2 100644 --- a/binding/python3/model_creation/contact_real.py +++ b/binding/python3/model_creation/contact_real.py @@ -32,16 +32,6 @@ def __init__( self.position = position if isinstance(position, np.ndarray) else np.array(position) self.axis = axis - # @property - # def mean_position(self) -> np.ndarray: - # """ - # Get the mean value (over time) of the marker position - # """ - # p = np.array(self.position) - # p = p if len(p.shape) == 1 else np.nanmean(p, axis=1) - # p = p if len(p.shape) == 1 else np.nanmean(p, axis=0) - # return p - @staticmethod def from_data( data: Data, @@ -68,8 +58,6 @@ def from_data( The axis of the contact """ - # TODO: Charbie - # Get the position of the contact points and do some sanity checks p: np.ndarray = function(data.values) if not isinstance(p, np.ndarray): @@ -86,7 +74,6 @@ def __str__(self): # Define the print function, so it automatically formats things in the file properly out_string = f"contact {self.name}\n" out_string += f"\tparent {self.parent_name}\n" - out_string += f"\tposition {self.position[0]:0.4f} {self.position[1]:0.4f} {self.position[2]:0.4f}\n" out_string += f"\taxis {self.axis.value}\n" out_string += "endcontact\n" diff --git a/binding/python3/model_creation/muscle.py b/binding/python3/model_creation/muscle.py new file mode 100644 index 000000000..78f83b28f --- /dev/null +++ b/binding/python3/model_creation/muscle.py @@ -0,0 +1,61 @@ +from typing import Callable +from enum import Enum + +from .protocols import Data +from .muscle_real import MuscleReal, MuscleType, MuscleStateType + + + +class Muscle: + def __init__( + self, + name: str, + type: MuscleType, + state_type: MuscleStateType, + muscle_group: str, + origin_position_function: Callable, + insertion_position_function: Callable, + optimal_length_function: Callable | float, + maximal_force_function: Callable | float, + tendon_slack_length_function: Callable | float, + pennation_angle_function: Callable | float, + maximal_excitation: float = 1, + ): + + """ + Parameters + ---------- + name + The name of the muscle + type + The type of the muscle + """ + self.name = name + self.type = type + self.state_type = state_type + self.muscle_group = muscle_group + self.origin_position_function = origin_position_function + self.insertion_position_function = insertion_position_function + self.optimal_length_function = optimal_length_function + self.maximal_force_function = maximal_force_function + self.tendon_slack_length_function = tendon_slack_length_function + self.pennation_angle_function = pennation_angle_function + self.maximal_excitation = maximal_excitation + + def to_muscle( + self, data: Data + ) -> MuscleReal: + return MuscleReal.from_data( + data, + self.name, + self.type, + self.state_type, + self.muscle_group, + self.origin_position_function, + self.insertion_position_function, + self.optimal_length_function, + self.maximal_force_function, + self.tendon_slack_length_function, + self.pennation_angle_function, + self.maximal_excitation, + ) \ No newline at end of file diff --git a/binding/python3/model_creation/muscle_group.py b/binding/python3/model_creation/muscle_group.py new file mode 100644 index 000000000..fe9eaee8f --- /dev/null +++ b/binding/python3/model_creation/muscle_group.py @@ -0,0 +1,32 @@ +from typing import Callable + +from .protocols import Data + +class MuscleGroup: + def __init__( + self, + name: str, + origin_parent_name: str, + insertion_parent_name: str, + ): + """ + Parameters + ---------- + name + The name of the new muscle group + origin_parent_name + The name of the parent segment for this muscle group + insertion_parent_name + The name of the insertion segment for this muscle group + """ + # Sanity checks + if not isinstance(name, str): + raise ValueError("The name of the muscle group must be a string.") + if not isinstance(origin_parent_name, str): + raise ValueError("The name of the origin parent segment must be a string.") + if not isinstance(insertion_parent_name, str): + raise ValueError("The name of the insertion parent segment must be a string.") + + self.name = name + self.origin_parent_name = origin_parent_name + self.insertion_parent_name = insertion_parent_name diff --git a/binding/python3/model_creation/muscle_real.py b/binding/python3/model_creation/muscle_real.py new file mode 100644 index 000000000..343cc3d40 --- /dev/null +++ b/binding/python3/model_creation/muscle_real.py @@ -0,0 +1,187 @@ +from typing import Callable + +import numpy as np +from enum import Enum + +from .protocols import Data + + +class MuscleType(Enum): + HILLTHELEN = "hillthelen" + # TODO: @pariterre to be completed + + +class MuscleStateType(Enum): + DEGROOTE = "DeGroote" + # TODO: @pariterre to be completed + + +class MuscleReal: + def __init__( + self, + name: str, + type: MuscleType, + state_type: MuscleStateType, + muscle_group: str, + origin_position: tuple[int | float, int | float, int | float] | np.ndarray, + insertion_position: tuple[int | float, int | float, int | float] | np.ndarray, + optimal_length: float, + maximal_force: float, + tendon_slack_length: float, + pennation_angle: float, + maximal_excitation: float, + ): + """ + Parameters + ---------- + name + The name of the new contact + type + The type of the muscle + state_type + The state type of the muscle + muscle_group + The muscle group the muscle belongs to + origin_position + The origin position of the muscle in the local reference frame of the origin segment @pariterre: please confirm + insertion_position + The insertion position of the muscle the local reference frame of the insertion segment @pariterre: please confirm + optimal_length + The optimal length of the muscle + maximal_force + The maximal force of the muscle can reach + tendon_slack_length + The length of the tendon at rest + pennation_angle + The pennation angle of the muscle + maximal_excitation + The maximal excitation of the muscle (usually 1.0, since it is normalized) + """ + self.name = name + self.type = type + self.state_type = state_type + self.muscle_group = muscle_group + self.origin_position = origin_position if isinstance(origin_position, np.ndarray) else np.array(origin_position) + self.insertion_position = insertion_position if isinstance(insertion_position, np.ndarray) else np.array(insertion_position) + self.optimal_length = optimal_length + self.maximal_force = maximal_force + self.tendon_slack_length = tendon_slack_length + self.pennation_angle = pennation_angle + self.maximal_excitation = maximal_excitation + + + @staticmethod + def from_data( + data: Data, + name: str, + type: MuscleType, + state_type: MuscleStateType, + muscle_group: str, + origin_position_function: Callable | np.ndarray[float], + insertion_position_function: Callable | np.ndarray[float], + optimal_length_function: Callable | float, + maximal_force_function: Callable | float, + tendon_slack_length_function: Callable | float, + pennation_angle_function: Callable | float, + maximal_excitation: float + ): + """ + This is a constructor for the Muscle class. It evaluates the function that defines the muscle to get an + actual position + + Parameters + ---------- + data + The data to pick the data from + name + The name of the muscle + type + The type of the muscle + state_type + The state type of the muscle + muscle_group + The muscle group the muscle belongs to + origin_position_function + The function (f(m) -> np.ndarray, where m is a dict of markers) that defines the origin position of the muscle + insertion_position_function + The function (f(m) -> np.ndarray, where m is a dict of markers) that defines the insertion position of the muscle + optimal_length_function + The function (f(m) -> float, where m is a dict of markers) that defines the optimal length of the muscle + maximal_force_function + The function (f(m) -> float, where m is a dict of markers) that defines the maximal force of the muscle + tendon_slack_length_function + The function (f(m) -> float, where m is a dict of markers) that defines the tendon slack length of the muscle + pennation_angle_function + The function (f(m) -> float, where m is a dict of markers) that defines the pennation angle of the muscle + maximal_excitation + The maximal excitation of the muscle (usually 1.0, since it is normalized) + """ + + origin_position: np.ndarray = origin_position_function(data.values) + if not isinstance(origin_position, np.ndarray): + raise RuntimeError(f"The origin_position_function {origin_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + if len(origin_position.shape) == 1: + origin_position = origin_position[:, np.newaxis] + if len(origin_position.shape) != 2 or origin_position.shape[0] != 3: + raise RuntimeError(f"The origin_position_function {origin_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + + insertion_position: np.ndarray = insertion_position_function(data.values) + if not isinstance(insertion_position, np.ndarray): + raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + if len(insertion_position.shape) == 1: + insertion_position = insertion_position[:, np.newaxis] + if len(origin_position) != 2 or insertion_position.shape[0] != 3: + raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + + optimal_length: float = optimal_length_function(data.values) + if not isinstance(optimal_length, float): + raise RuntimeError(f"The optimal_length_function {optimal_length_function} must return a float") + + maximal_force: float = maximal_force_function(data.values) + if not isinstance(maximal_force, float): + raise RuntimeError(f"The maximal_force_function {maximal_force_function} must return a float") + + tendon_slack_length: float = tendon_slack_length_function(data.values) + if not isinstance(tendon_slack_length, float): + raise RuntimeError(f"The tendon_slack_length_function {tendon_slack_length_function} must return a float") + + pennation_angle: float = pennation_angle_function(data.values) + if not isinstance(pennation_angle, float): + raise RuntimeError(f"The pennation_angle_function {pennation_angle_function} must return a float") + + return MuscleReal(name, + type, + state_type, + muscle_group, + origin_position, + insertion_position, + optimal_length, + maximal_force, + tendon_slack_length, + pennation_angle, + maximal_excitation) + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + out_string = f"muscle {self.name}\n" + out_string += f"\ttype: {self.type}\n" + out_string += f"\tstatetype {self.state_type}\n" + out_string += f"\tmusclegroup {self.muscle_group}\n" + out_string += f"\toriginposition {self.origin_position}\n" + out_string += f"\tinsertionposition {self.insertion_position}\n" + out_string += f"\toptimallength {self.optimal_length}\n" + out_string += f"\tmaximalforce {self.maximal_force}\n" + out_string += f"\ttendonslacklength {self.tendon_slack_length}\n" + out_string += f"\tpennationangle {self.penation_angle}\n" + out_string += "endmuscle\n" + + # Define each muscle's via points + for i_viapoint, via_point in enumerate(self.via_points): + out_string += f"viapoint {via_point.name}\n" + out_string += f"\tparent {via_point.parent_name}\n" + out_string += f"\tmuscle {via_point.muscle_name}\n" + out_string += f"\tmusclegroup {via_point.muscle_group}\n" + out_string += f"\tposition {via_point.position}\n" + out_string += "endviapoint\n" + + return out_string diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 66a331a7a..46b3606a4 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -11,7 +11,7 @@ class Segment: def __init__( self, - name: str = None, + name, parent_name: str = "", translations: Translations = Translations.NONE, rotations: Rotations = Rotations.NONE, From 14d12a0bd1f0892ba9ea1271df76d23678793b55 Mon Sep 17 00:00:00 2001 From: Charbie Date: Wed, 15 Jan 2025 15:12:27 +0400 Subject: [PATCH 04/10] added via points --- binding/python3/model_creation/__init__.py | 2 + .../model_creation/biomechanical_model.py | 31 ++++--- .../biomechanical_model_real.py | 30 +++++-- binding/python3/model_creation/muscle.py | 11 +-- binding/python3/model_creation/via_point.py | 47 ++++++++++ .../python3/model_creation/via_point_real.py | 86 +++++++++++++++++++ 6 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 binding/python3/model_creation/via_point.py create mode 100644 binding/python3/model_creation/via_point_real.py diff --git a/binding/python3/model_creation/__init__.py b/binding/python3/model_creation/__init__.py index b0d4001f1..1701cab08 100644 --- a/binding/python3/model_creation/__init__.py +++ b/binding/python3/model_creation/__init__.py @@ -8,6 +8,7 @@ from .contact import Contact from .muscle import Muscle from .muscle_group import MuscleGroup +from .via_point import ViaPoint from .mesh import Mesh from .protocols import Data, GenericDynamicModel from .rotations import Rotations @@ -22,6 +23,7 @@ from .marker_real import MarkerReal from .contact_real import ContactReal from .muscle_real import MuscleReal, MuscleType, MuscleStateType +from .via_point_real import ViaPointReal from .mesh_real import MeshReal from .segment_real import SegmentReal from .segment_coordinate_system_real import SegmentCoordinateSystemReal diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 596475577..1407ed217 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -11,6 +11,7 @@ def __init__(self, bio_sym_path: str = None): self.segments = {} self.muscle_groups = {} self.muscles = {} + self.via_points = {} if bio_sym_path is None: return @@ -73,26 +74,33 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: insertion_parent_name=mg.insertion_parent_name, ) + return model + + + def add_real_muscles(self, model: BiomechanicalModelReal, data: Data): + for name in self.muscles: m = self.muscles[name] if m.muscle_group not in model.muscle_groups: raise RuntimeError(f"Please create the muscle group {m.muscle_group} before putting the muscle {m.name} in it.") - model.muscles[m.name] = MuscleReal( - name=m.name, - type=m.type, - state_type=m.state_type, - muscle_group=m.muscle_group, - origin_position=m.origin_position, - insertion_position=m.insertion_position, - optimal_length=m.optimal_length, - maximal_force=m.maximal_force, - tendon_slack_length=m.tendon_slack_length, - penation_angle=m.penation_angle) + model.muscles[m.name] = m.to_muscle(model, data) + + for name in self.via_points: + vp = self.via_points[name] + + if vp.muscle_name not in model.muscles: + raise RuntimeError(f"Please create the muscle {vp.muscle_name} before putting the via point {vp.name} in it.") + + if vp.muscle_group not in model.muscle_groups: + raise RuntimeError(f"Please create the muscle group {vp.muscle_group} before putting the via point {vp.name} in it.") + + model.via_points[vp.name] = vp.to_via_point(data) return model + def write(self, save_path: str, data: Data): """ Collapse the model to an actual personalized biomechanical model based on the generic model and the data @@ -106,4 +114,5 @@ def write(self, save_path: str, data: Data): The data to collapse the model from """ model = self.to_real(data) + model = self.add_real_muscles(model, data) model.write(save_path) diff --git a/binding/python3/model_creation/biomechanical_model_real.py b/binding/python3/model_creation/biomechanical_model_real.py index ad7407d08..3b257c8b3 100644 --- a/binding/python3/model_creation/biomechanical_model_real.py +++ b/binding/python3/model_creation/biomechanical_model_real.py @@ -3,36 +3,48 @@ def __init__(self): from .segment_real import SegmentReal # Imported here to prevent from circular imports from .muscle_group import MuscleGroup from .muscle_real import MuscleReal + from .via_point_real import ViaPointReal self.segments: dict[str:SegmentReal, ...] = {} # From Pythom 3.7 the insertion order in a dict is preserved. This is important because when writing a new # .bioMod file, the order of the segment matters self.muscle_groups: dict[str:MuscleGroup, ...] = {} self.muscles: dict[str:MuscleReal, ...] = {} + self.via_points: dict[str:ViaPointReal, ...] = {} def __str__(self): out_string = "version 4\n\n" - out_string += "// --------------------------------------------------------------" - out_string += "// SEGMENTS" - out_string += "// --------------------------------------------------------------" + out_string += "// --------------------------------------------------------------\n" + out_string += "// SEGMENTS\n" + out_string += "// --------------------------------------------------------------\n\n" for name in self.segments: out_string += str(self.segments[name]) out_string += "\n\n\n" # Give some space between segments - out_string += "// --------------------------------------------------------------" - out_string += "// MUSCLE GROUPS" - out_string += "// --------------------------------------------------------------" + out_string += "// --------------------------------------------------------------\n" + out_string += "// MUSCLE GROUPS\n" + out_string += "// --------------------------------------------------------------\n\n" for name in self.muscle_groups: out_string += str(self.muscle_groups[name]) + out_string += "\n" + out_string += "\n\n\n" # Give some space after muscle groups - out_string += "// --------------------------------------------------------------" - out_string += "// MUSCLES" - out_string += "// --------------------------------------------------------------" + out_string += "// --------------------------------------------------------------\n" + out_string += "// MUSCLES\n" + out_string += "// --------------------------------------------------------------\n\n" for name in self.muscles: out_string += str(self.muscles[name]) out_string += "\n\n\n" # Give some space between muscles + + out_string += "// --------------------------------------------------------------\n" + out_string += "// MUSCLES VIA POINTS\n" + out_string += "// --------------------------------------------------------------\n\n" + for name in self.via_points: + out_string += str(self.via_points[name]) + out_string += "\n\n\n" # Give some space between via points + return out_string def write(self, file_path: str): diff --git a/binding/python3/model_creation/muscle.py b/binding/python3/model_creation/muscle.py index 78f83b28f..c7a2f4d77 100644 --- a/binding/python3/model_creation/muscle.py +++ b/binding/python3/model_creation/muscle.py @@ -10,7 +10,7 @@ class Muscle: def __init__( self, name: str, - type: MuscleType, + muscle_type: MuscleType, state_type: MuscleStateType, muscle_group: str, origin_position_function: Callable, @@ -27,11 +27,11 @@ def __init__( ---------- name The name of the muscle - type + muscle_type The type of the muscle """ self.name = name - self.type = type + self.muscle_type = muscle_type self.state_type = state_type self.muscle_group = muscle_group self.origin_position_function = origin_position_function @@ -43,12 +43,13 @@ def __init__( self.maximal_excitation = maximal_excitation def to_muscle( - self, data: Data + self, model, data: Data ) -> MuscleReal: return MuscleReal.from_data( data, + model, self.name, - self.type, + self.muscle_type, self.state_type, self.muscle_group, self.origin_position_function, diff --git a/binding/python3/model_creation/via_point.py b/binding/python3/model_creation/via_point.py new file mode 100644 index 000000000..11ee5f04c --- /dev/null +++ b/binding/python3/model_creation/via_point.py @@ -0,0 +1,47 @@ +from typing import Callable + +from .protocols import Data +from .via_point_real import ViaPointReal + +class ViaPoint: + def __init__( + self, + name: str, + position_function: Callable | str = None, + parent_name: str = None, + muscle_name: str = None, + muscle_group: str = None, + ): + """ + Parameters + ---------- + name + The name of the new via point + position_function + The function (f(m) -> np.ndarray, where m is a dict of markers) that defines the via point with. + parent_name + The name of the parent the via point is attached to + muscle_name + The name of the muscle that passes through this via point + muscle_group + The muscle group the muscle belongs to + """ + self.name = name + position_function = position_function if position_function is not None else self.name + self.position_function = (lambda m, bio: m[position_function]) if isinstance(position_function, str) else position_function + self.parent_name = parent_name + self.muscle_name = muscle_name + self.muscle_group = muscle_group + + + def to_via_point( + self, data: Data + ) -> ViaPointReal: + return ViaPointReal.from_data( + data, + self.name, + self.parent_name, + self.muscle_name, + self.muscle_group, + self.position_function, + ) \ No newline at end of file diff --git a/binding/python3/model_creation/via_point_real.py b/binding/python3/model_creation/via_point_real.py new file mode 100644 index 000000000..ce7eb910f --- /dev/null +++ b/binding/python3/model_creation/via_point_real.py @@ -0,0 +1,86 @@ +from typing import Callable + +import numpy as np + +from .protocols import Data + +class ViaPointReal: + def __init__( + self, + name: str, + parent_name: str, + muscle_name: str, + muscle_group: str, + position: tuple[int | float, int | float, int | float] | np.ndarray = None, + ): + """ + Parameters + ---------- + name + The name of the new via point + parent_name + The name of the parent the via point is attached to + muscle_name + The name of the muscle that passes through this via point + muscle_group + The muscle group the muscle belongs to + position + The 3d position of the via point in the local reference frame + """ + self.name = name + self.parent_name = parent_name + self.muscle_name = muscle_name + self.muscle_group = muscle_group + if position is None: + position = np.array((0, 0, 0, 1)) + self.position = position if isinstance(position, np.ndarray) else np.array(position) + + @staticmethod + def from_data( + data: Data, + name: str, + parent_name: str, + muscle_name: str, + muscle_group: str, + position_function: Callable, + ): + """ + This is a constructor for the Contact class. It evaluates the function that defines the contact to get an + actual position + + Parameters + ---------- + data + The data to pick the data from + name + The name of the new via point + parent_name + The name of the parent the via point is attached to + muscle_name + The name of the muscle that passes through this via point + muscle_group + The muscle group the muscle belongs to + position_function + The function (f(m) -> np.ndarray, where m is a dict of markers (XYZ1 x time)) that defines the via point in the local joint coordinates. + """ + + # Get the position of the contact points and do some sanity checks + position: np.ndarray = position_function(data.values) + if not isinstance(position, np.ndarray): + raise RuntimeError(f"The function {position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + if position.shape == (3, 1): + position = position.reshape((3, )) + elif position.shape != (3, ): + raise RuntimeError(f"The function {position_function} must return a vector of dimension 3 (XYZ)") + + return ViaPointReal(name, parent_name, muscle_name, muscle_group, position) + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + out_string = f"viapoint\t{self.name}\n" + out_string += f"\tparent\t{self.parent_name}\n" + out_string += f"\tmuscle\t{self.muscle_name}\n" + out_string += f"\tmusclegroup\t{self.muscle_group}\n" + out_string += f"\tposition\t{np.round(self.position[0], 4)}\t{np.round(self.position[1], 4)}\t{np.round(self.position[2], 4)}\n" + out_string += "endviapoint\n" + return out_string From bbdf859b23c346cfe16c9e38451ded898baef96a Mon Sep 17 00:00:00 2001 From: Charbie Date: Wed, 15 Jan 2025 15:12:57 +0400 Subject: [PATCH 05/10] added mesh files --- binding/python3/model_creation/__init__.py | 2 + .../model_creation/biomechanical_model.py | 5 + binding/python3/model_creation/mesh_file.py | 53 +++++++ .../python3/model_creation/mesh_file_real.py | 134 ++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 binding/python3/model_creation/mesh_file.py create mode 100644 binding/python3/model_creation/mesh_file_real.py diff --git a/binding/python3/model_creation/__init__.py b/binding/python3/model_creation/__init__.py index 1701cab08..d6fad3f07 100644 --- a/binding/python3/model_creation/__init__.py +++ b/binding/python3/model_creation/__init__.py @@ -10,6 +10,7 @@ from .muscle_group import MuscleGroup from .via_point import ViaPoint from .mesh import Mesh +from .mesh_file import MeshFile from .protocols import Data, GenericDynamicModel from .rotations import Rotations from .range_of_motion import RangeOfMotion, Ranges @@ -25,6 +26,7 @@ from .muscle_real import MuscleReal, MuscleType, MuscleStateType from .via_point_real import ViaPointReal from .mesh_real import MeshReal +from .mesh_file_real import MeshFileReal from .segment_real import SegmentReal from .segment_coordinate_system_real import SegmentCoordinateSystemReal from .inertia_parameters_real import InertiaParametersReal diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 1407ed217..4c289188e 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -47,6 +47,10 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: if s.mesh is not None: mesh = s.mesh.to_mesh(data, model, scs) + mesh_file = None + if s.mesh_file is not None: + mesh_file = s.mesh_file.to_mesh_file(data) + model.segments[s.name] = SegmentReal( name=s.name, parent_name=s.parent_name, @@ -57,6 +61,7 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: qdot_ranges=s.qdot_ranges, inertia_parameters=inertia_parameters, mesh=mesh, + mesh_file=mesh_file, ) for marker in s.markers: diff --git a/binding/python3/model_creation/mesh_file.py b/binding/python3/model_creation/mesh_file.py new file mode 100644 index 000000000..9604faa82 --- /dev/null +++ b/binding/python3/model_creation/mesh_file.py @@ -0,0 +1,53 @@ +from typing import Callable + +import numpy as np + +from .biomechanical_model_real import BiomechanicalModelReal +from .mesh_file_real import MeshFileReal +from .protocols import Data +from .segment_coordinate_system_real import SegmentCoordinateSystemReal + + +class MeshFile: + def __init__( + self, + mesh_file_name: str, + mesh_color: np.ndarray[float] | list[float] | tuple[float] = None, + scaling_function: Callable = None, + rotation_function: Callable = None, + translation_function: Callable = None, + ): + """ + This is a pre-constructor for the MeshFileReal class. It allows to create a generic model by marker names + + Parameters + ---------- + mesh_file_name + The name of the mesh file + mesh_color + The color the mesh should be displayed in (RGB) + scaling_function + The function that defines the scaling of the mesh + rotation_function + The function that defines the rotation of the mesh + translation_function + The function that defines the translation of the mesh + """ + self.mesh_file_name = mesh_file_name + self.mesh_color = mesh_color + self.scaling_function = scaling_function + self.rotation_function = rotation_function + self.translation_function = translation_function + + + def to_mesh_file( + self, data: Data, + ) -> MeshFileReal: + return MeshFileReal.from_data( + data, + self.mesh_file_name, + self.mesh_color, + self.scaling_function, + self.rotation_function, + self.translation_function, + ) diff --git a/binding/python3/model_creation/mesh_file_real.py b/binding/python3/model_creation/mesh_file_real.py new file mode 100644 index 000000000..97ab82e23 --- /dev/null +++ b/binding/python3/model_creation/mesh_file_real.py @@ -0,0 +1,134 @@ +from typing import Callable + +import numpy as np + +from .biomechanical_model_real import BiomechanicalModelReal +from .protocols import Data + + +class MeshFileReal: + def __init__( + self, + mesh_file_name: str, + mesh_color: np.ndarray[float] | list[float] | tuple[float], + mesh_scale: np.ndarray[float] | list[float] | tuple[float], + mesh_rotation: np.ndarray[float] | list[float] | tuple[float], + mesh_translation: np.ndarray[float] | list[float] | tuple[float], + ): + """ + Parameters + ---------- + mesh_file_name + The name of the mesh file + mesh_color + The color the mesh should be displayed in (RGB) + mesh_scale + The scaling that must be applied to the mesh (XYZ) + mesh_rotation + The rotation that must be applied to the mesh (Euler angles: XYZ) + mesh_translation + The translation that must be applied to the mesh (XYZ) + """ + + self.mesh_file_name = mesh_file_name + self.mesh_color = mesh_color + self.mesh_scale = mesh_scale + self.mesh_rotation = mesh_rotation + self.mesh_translation = mesh_translation + + @staticmethod + def from_data( + data: Data, + mesh_file_name: str, + mesh_color: np.ndarray[float] | list[float] | tuple[float] = None, + scaling_function: Callable = None, + rotation_function: Callable = None, + translation_function: Callable = None, + ): + """ + This is a constructor for the MeshFileReal class. It evaluates the functions that defines the mesh file to get + actual characteristics of the mesh + + Parameters + ---------- + data + The data to pick the data from + mesh_file_name + The name of the mesh file + mesh_color + The color the mesh should be displayed in (RGB) + scaling_function + The function (f(m) -> np.ndarray, where m is a dict of markers (XYZ1 x time)) that defines the scaling + rotation_function + The function (f(m) -> np.ndarray, where m is a dict of markers (XYZ1 x time)) that defines the rotation + translation_function + The function (f(m) -> np.ndarray, where m is a dict of markers (XYZ1 x time)) that defines the translation + """ + + if not isinstance(mesh_file_name, str): + raise RuntimeError("The mesh_file_name must be a string") + + if mesh_color is not None: + mesh_color = np.array(mesh_color) + if mesh_color.shape == (3, 1): + mesh_color = mesh_color.reshape((3,)) + elif mesh_color.shape != (3,): + raise RuntimeError("The mesh_color must be a vector of dimension 3 (RGB)") + + if scaling_function is None: + mesh_scale = np.array([1, 1, 1]) + else: + mesh_scale: np.ndarray = scaling_function(data.values) + if not isinstance(mesh_scale, np.ndarray): + raise RuntimeError(f"The scaling_function {scaling_function} must return a vector of dimension 3 (XYZ)") + if mesh_scale.shape == (3, 1): + mesh_scale = mesh_scale.reshape((3,)) + elif mesh_scale.shape != (3,): + raise RuntimeError( + f"The scaling_function {scaling_function} must return a vector of dimension 3 (XYZ)") + + if rotation_function is None: + mesh_rotation = np.array([0, 0, 0]) + else: + mesh_rotation: np.ndarray = rotation_function(data.values) + if not isinstance(mesh_rotation, np.ndarray): + raise RuntimeError(f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)") + if mesh_rotation.shape == (3, 1): + mesh_rotation = mesh_rotation.reshape((3,)) + elif mesh_rotation.shape != (3,): + raise RuntimeError( + f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)") + + if translation_function is None: + mesh_translation = np.array([0, 0, 0]) + else: + mesh_translation: np.ndarray = translation_function(data.values) + if not isinstance(mesh_translation, np.ndarray): + raise RuntimeError(f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)") + if mesh_translation.shape == (3, 1): + mesh_translation = mesh_translation.reshape((3,)) + elif mesh_translation.shape != (3,): + raise RuntimeError( + f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)") + + return MeshFileReal( + mesh_file_name=mesh_file_name, + mesh_color=mesh_color, + mesh_scale=mesh_scale, + mesh_rotation=mesh_rotation, + mesh_translation=mesh_translation, + ) + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + out_string = "" + out_string += f"\tmeshfile\t{self.mesh_file_name}\n" + if self.mesh_color is not None: + out_string += f"\tmeshcolor\t{self.mesh_color[0]}\t{self.mesh_color[1]}\t{self.mesh_color[2]}\n" + if self.mesh_scale is not None: + out_string += f"\tmeshscale\t{self.mesh_scale[0]}\t{self.mesh_scale[1]}\t{self.mesh_scale[2]}\n" + if self.mesh_rotation is not None and self.mesh_translation is not None: + out_string += f"\tmeshrt\t{self.mesh_rotation[0]}\t{self.mesh_rotation[1]}\t{self.mesh_rotation[2]}\txyz\t{self.mesh_translation[0]}\t{self.mesh_translation[1]}\t{self.mesh_translation[2]}\n" + elif self.mesh_rotation is not None or self.mesh_translation is not None: + raise RuntimeError("The mesh_rotation and mesh_translation must be both defined or both undefined") + return out_string From 30f792c2bef58383dac3211bae50b3c016fd2186 Mon Sep 17 00:00:00 2001 From: Charbie Date: Wed, 15 Jan 2025 15:13:23 +0400 Subject: [PATCH 06/10] tab instead of spaces --- .../python3/model_creation/contact_real.py | 17 ++-- .../model_creation/inertia_parameters_real.py | 6 +- binding/python3/model_creation/marker_real.py | 10 +-- binding/python3/model_creation/mesh_real.py | 2 +- .../python3/model_creation/muscle_group.py | 9 +++ binding/python3/model_creation/muscle_real.py | 81 +++++++++---------- .../python3/model_creation/range_of_motion.py | 10 +-- binding/python3/model_creation/segment.py | 17 ++-- .../python3/model_creation/segment_real.py | 15 ++-- 9 files changed, 90 insertions(+), 77 deletions(-) diff --git a/binding/python3/model_creation/contact_real.py b/binding/python3/model_creation/contact_real.py index 7c9c1e2c2..34927e2d1 100644 --- a/binding/python3/model_creation/contact_real.py +++ b/binding/python3/model_creation/contact_real.py @@ -62,19 +62,18 @@ def from_data( p: np.ndarray = function(data.values) if not isinstance(p, np.ndarray): raise RuntimeError(f"The function {function} must return a np.ndarray of dimension 3xT (XYZ x time)") - if len(p.shape) == 1: - p = p[:, np.newaxis] - - if len(p.shape) != 2 or p.shape[0] != 3: - raise RuntimeError(f"The function {function} must return a np.ndarray of dimension 3xT (XYZ x time)") + if p.shape == (3, 1): + p = p.reshape((3, )) + elif p.shape != (3, ): + raise RuntimeError(f"The function {function} must return a vector of dimension 3 (XYZ)") return ContactReal(name, parent_name, p, axis) def __str__(self): # Define the print function, so it automatically formats things in the file properly - out_string = f"contact {self.name}\n" - out_string += f"\tparent {self.parent_name}\n" - out_string += f"\tposition {self.position[0]:0.4f} {self.position[1]:0.4f} {self.position[2]:0.4f}\n" - out_string += f"\taxis {self.axis.value}\n" + out_string = f"contact\t{self.name}\n" + out_string += f"\tparent\t{self.parent_name}\n" + out_string += f"\tposition\t{np.round(self.position[0], 4)}\t{np.round(self.position[1], 4)}\t{np.round(self.position[2], 4)}\n" + out_string += f"\taxis\t{self.axis.value}\n" out_string += "endcontact\n" return out_string diff --git a/binding/python3/model_creation/inertia_parameters_real.py b/binding/python3/model_creation/inertia_parameters_real.py index cb864c6a2..4fd31f36e 100644 --- a/binding/python3/model_creation/inertia_parameters_real.py +++ b/binding/python3/model_creation/inertia_parameters_real.py @@ -81,7 +81,7 @@ def __str__(self): # Define the print function, so it automatically formats things in the file properly com = np.nanmean(self.center_of_mass, axis=1)[:3] - out_string = f"\tmass {self.mass}\n" - out_string += f"\tCenterOfMass {com[0]:0.5f} {com[1]:0.5f} {com[2]:0.5f}\n" - out_string += f"\tinertia_xxyyzz {self.inertia[0]} {self.inertia[1]} {self.inertia[2]}\n" + out_string = f"\tmass\t{self.mass}\n" + out_string += f"\tCenterOfMass\t{com[0]:0.5f}\t{com[1]:0.5f}\t{com[2]:0.5f}\n" + out_string += f"\tinertia_xxyyzz\t{self.inertia[0]}\t{self.inertia[1]}\t{self.inertia[2]}\n" return out_string diff --git a/binding/python3/model_creation/marker_real.py b/binding/python3/model_creation/marker_real.py index e3f3b4f1d..ba8a7c81e 100644 --- a/binding/python3/model_creation/marker_real.py +++ b/binding/python3/model_creation/marker_real.py @@ -101,13 +101,13 @@ def mean_position(self) -> np.ndarray: def __str__(self): # Define the print function, so it automatically formats things in the file properly - out_string = f"marker {self.name}\n" - out_string += f"\tparent {self.parent_name}\n" + out_string = f"marker\t{self.name}\n" + out_string += f"\tparent\t{self.parent_name}\n" p = self.mean_position - out_string += f"\tposition {p[0]:0.4f} {p[1]:0.4f} {p[2]:0.4f}\n" - out_string += f"\ttechnical {1 if self.is_technical else 0}\n" - out_string += f"\tanatomical {1 if self.is_anatomical else 0}\n" + out_string += f"\tposition\t{p[0]:0.4f}\t{p[1]:0.4f}\t{p[2]:0.4f}\n" + out_string += f"\ttechnical\t{1 if self.is_technical else 0}\n" + out_string += f"\tanatomical\t{1 if self.is_anatomical else 0}\n" out_string += "endmarker\n" return out_string diff --git a/binding/python3/model_creation/mesh_real.py b/binding/python3/model_creation/mesh_real.py index 312239b97..1aef44b1f 100644 --- a/binding/python3/model_creation/mesh_real.py +++ b/binding/python3/model_creation/mesh_real.py @@ -72,5 +72,5 @@ def __str__(self): # Do a sanity check position = np.array(position) p = position if len(position.shape) == 1 else np.nanmean(position, axis=1) - out_string += f"\tmesh {p[0]:0.4f} {p[1]:0.4f} {p[2]:0.4f}\n" + out_string += f"\tmesh\t{p[0]:0.4f}\t{p[1]:0.4f}\t{p[2]:0.4f}\n" return out_string diff --git a/binding/python3/model_creation/muscle_group.py b/binding/python3/model_creation/muscle_group.py index fe9eaee8f..8ff3c0c03 100644 --- a/binding/python3/model_creation/muscle_group.py +++ b/binding/python3/model_creation/muscle_group.py @@ -30,3 +30,12 @@ def __init__( self.name = name self.origin_parent_name = origin_parent_name self.insertion_parent_name = insertion_parent_name + + def __str__(self): + # Define the print function, so it automatically formats things in the file properly + out_string = f"musclegroup\t{self.name}\n" + out_string += f"\tOriginParent\t{self.origin_parent_name}\n" + out_string += f"\tInsertionParent\t{self.insertion_parent_name}\n" + out_string += "endmusclegroup\n" + return out_string + diff --git a/binding/python3/model_creation/muscle_real.py b/binding/python3/model_creation/muscle_real.py index 343cc3d40..e61dca517 100644 --- a/binding/python3/model_creation/muscle_real.py +++ b/binding/python3/model_creation/muscle_real.py @@ -4,6 +4,7 @@ from enum import Enum from .protocols import Data +from .via_point_real import ViaPointReal class MuscleType(Enum): @@ -20,7 +21,7 @@ class MuscleReal: def __init__( self, name: str, - type: MuscleType, + muscle_type: MuscleType, state_type: MuscleStateType, muscle_group: str, origin_position: tuple[int | float, int | float, int | float] | np.ndarray, @@ -30,13 +31,14 @@ def __init__( tendon_slack_length: float, pennation_angle: float, maximal_excitation: float, + via_points: list[ViaPointReal] = None, ): """ Parameters ---------- name The name of the new contact - type + muscle_type The type of the muscle state_type The state type of the muscle @@ -56,9 +58,11 @@ def __init__( The pennation angle of the muscle maximal_excitation The maximal excitation of the muscle (usually 1.0, since it is normalized) + via_points + The via points of the muscle """ self.name = name - self.type = type + self.muscle_type = muscle_type self.state_type = state_type self.muscle_group = muscle_group self.origin_position = origin_position if isinstance(origin_position, np.ndarray) else np.array(origin_position) @@ -68,13 +72,15 @@ def __init__( self.tendon_slack_length = tendon_slack_length self.pennation_angle = pennation_angle self.maximal_excitation = maximal_excitation + self.via_points = via_points @staticmethod def from_data( data: Data, + model, name: str, - type: MuscleType, + muscle_type: MuscleType, state_type: MuscleStateType, muscle_group: str, origin_position_function: Callable | np.ndarray[float], @@ -95,7 +101,7 @@ def from_data( The data to pick the data from name The name of the muscle - type + muscle_type The type of the muscle state_type The state type of the muscle @@ -106,34 +112,33 @@ def from_data( insertion_position_function The function (f(m) -> np.ndarray, where m is a dict of markers) that defines the insertion position of the muscle optimal_length_function - The function (f(m) -> float, where m is a dict of markers) that defines the optimal length of the muscle + The function (f(model, m) -> float, where m is a dict of markers) that defines the optimal length of the muscle maximal_force_function The function (f(m) -> float, where m is a dict of markers) that defines the maximal force of the muscle tendon_slack_length_function - The function (f(m) -> float, where m is a dict of markers) that defines the tendon slack length of the muscle + The function (f(model, m) -> float, where m is a dict of markers) that defines the tendon slack length of the muscle pennation_angle_function - The function (f(m) -> float, where m is a dict of markers) that defines the pennation angle of the muscle + The function (f(model, m) -> float, where m is a dict of markers) that defines the pennation angle of the muscle maximal_excitation The maximal excitation of the muscle (usually 1.0, since it is normalized) """ - origin_position: np.ndarray = origin_position_function(data.values) if not isinstance(origin_position, np.ndarray): - raise RuntimeError(f"The origin_position_function {origin_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") - if len(origin_position.shape) == 1: - origin_position = origin_position[:, np.newaxis] - if len(origin_position.shape) != 2 or origin_position.shape[0] != 3: - raise RuntimeError(f"The origin_position_function {origin_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + raise RuntimeError(f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)") + if origin_position.shape == (3, 1): + origin_position = origin_position.reshape((3,)) + elif origin_position.shape != (3,): + raise RuntimeError(f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)") insertion_position: np.ndarray = insertion_position_function(data.values) if not isinstance(insertion_position, np.ndarray): - raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") - if len(insertion_position.shape) == 1: - insertion_position = insertion_position[:, np.newaxis] - if len(origin_position) != 2 or insertion_position.shape[0] != 3: - raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)") + if insertion_position.shape == (3, 1): + insertion_position = insertion_position.reshape((3,)) + elif insertion_position.shape != (3,): + raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)") - optimal_length: float = optimal_length_function(data.values) + optimal_length: float = optimal_length_function(model, data.values) if not isinstance(optimal_length, float): raise RuntimeError(f"The optimal_length_function {optimal_length_function} must return a float") @@ -141,16 +146,16 @@ def from_data( if not isinstance(maximal_force, float): raise RuntimeError(f"The maximal_force_function {maximal_force_function} must return a float") - tendon_slack_length: float = tendon_slack_length_function(data.values) + tendon_slack_length: float = tendon_slack_length_function(model, data.values) if not isinstance(tendon_slack_length, float): raise RuntimeError(f"The tendon_slack_length_function {tendon_slack_length_function} must return a float") - pennation_angle: float = pennation_angle_function(data.values) + pennation_angle: float = pennation_angle_function(model, data.values) if not isinstance(pennation_angle, float): raise RuntimeError(f"The pennation_angle_function {pennation_angle_function} must return a float") return MuscleReal(name, - type, + muscle_type, state_type, muscle_group, origin_position, @@ -163,25 +168,15 @@ def from_data( def __str__(self): # Define the print function, so it automatically formats things in the file properly - out_string = f"muscle {self.name}\n" - out_string += f"\ttype: {self.type}\n" - out_string += f"\tstatetype {self.state_type}\n" - out_string += f"\tmusclegroup {self.muscle_group}\n" - out_string += f"\toriginposition {self.origin_position}\n" - out_string += f"\tinsertionposition {self.insertion_position}\n" - out_string += f"\toptimallength {self.optimal_length}\n" - out_string += f"\tmaximalforce {self.maximal_force}\n" - out_string += f"\ttendonslacklength {self.tendon_slack_length}\n" - out_string += f"\tpennationangle {self.penation_angle}\n" + out_string = f"muscle\t{self.name}\n" + out_string += f"\ttype\t{self.muscle_type.value}\n" + out_string += f"\tstatetype\t{self.state_type.value}\n" + out_string += f"\tmusclegroup\t{self.muscle_group}\n" + out_string += f"\toriginposition\t{np.round(self.origin_position[0], 4)}\t{np.round(self.origin_position[1], 4)}\t{np.round(self.origin_position[2], 4)}\n" + out_string += f"\tinsertionposition\t{np.round(self.insertion_position[0], 4)}\t{np.round(self.insertion_position[1], 4)}\t{np.round(self.insertion_position[2], 4)}\n" + out_string += f"\toptimallength\t{self.optimal_length:0.4f}\n" + out_string += f"\tmaximalforce\t{self.maximal_force:0.4f}\n" + out_string += f"\ttendonslacklength\t{self.tendon_slack_length:0.4f}\n" + out_string += f"\tpennationangle\t{self.pennation_angle:0.4f}\n" out_string += "endmuscle\n" - - # Define each muscle's via points - for i_viapoint, via_point in enumerate(self.via_points): - out_string += f"viapoint {via_point.name}\n" - out_string += f"\tparent {via_point.parent_name}\n" - out_string += f"\tmuscle {via_point.muscle_name}\n" - out_string += f"\tmusclegroup {via_point.muscle_group}\n" - out_string += f"\tposition {via_point.position}\n" - out_string += "endviapoint\n" - return out_string diff --git a/binding/python3/model_creation/range_of_motion.py b/binding/python3/model_creation/range_of_motion.py index 3d7503882..4c59bdb11 100644 --- a/binding/python3/model_creation/range_of_motion.py +++ b/binding/python3/model_creation/range_of_motion.py @@ -9,22 +9,22 @@ class Ranges(Enum): class RangeOfMotion: def __init__(self, - type: Ranges, + range_type: Ranges, min_bound: list[float], max_bound: list[float]): - self.type = type + self.range_type = range_type self.min_bound = min_bound self.max_bound = max_bound def __str__(self): # Define the print function, so it automatically formats things in the file properly - if self.type == Ranges.Q: + if self.range_type == Ranges.Q: out_string = f"\trangesQ \n" - elif self.type == Ranges.Qdot: + elif self.range_type == Ranges.Qdot: out_string = f"\trangesQdot \n" else: - raise RuntimeError("RangeOfMotion's type must be Range.Q or Ranges.Qdot") + raise RuntimeError("RangeOfMotion's range_type must be Range.Q or Ranges.Qdot") for i_dof in range(len(self.min_bound)): out_string += f"\t\t{self.min_bound[i_dof]:0.5f}\t{self.max_bound[i_dof]:0.5f}\n" diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 46b3606a4..05824d0b0 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -2,6 +2,7 @@ from .marker import Marker from .contact import Contact from .mesh import Mesh +from .mesh_file import MeshFile from .rotations import Rotations from .range_of_motion import RangeOfMotion, Ranges from .segment_coordinate_system import SegmentCoordinateSystem @@ -20,6 +21,7 @@ def __init__( segment_coordinate_system: SegmentCoordinateSystem = None, inertia_parameters: InertiaParameters = None, mesh: Mesh = None, + mesh_file: MeshFile = None, ): """ Create a new generic segment. @@ -38,6 +40,8 @@ def __init__( The inertia parameters of the segment mesh The mesh points of the segment + mesh_file + The mesh file of the segment """ self.name = name @@ -51,6 +55,7 @@ def __init__( self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters self.mesh = mesh + self.mesh_file = mesh_file def add_marker(self, marker: Marker): """ @@ -87,7 +92,7 @@ def add_contact(self, contact: Contact): contact.parent_name = self.name self.contacts.append(contact) - def add_range(self, type: Ranges, min_bound, max_bound): + def add_range(self, range_type: Ranges, min_bound, max_bound): """ Add a new rangeQ to the segment @@ -96,9 +101,9 @@ def add_range(self, type: Ranges, min_bound, max_bound): marker The marker to add """ - if type == Ranges.Q: - self.q_ranges = RangeOfMotion(type=type, min_bound=min_bound, max_bound=max_bound) - elif type == Ranges.Qdot: - self.qdot_ranges = RangeOfMotion(type=type, min_bound=min_bound, max_bound=max_bound) + if range_type == Ranges.Q: + self.q_ranges = RangeOfMotion(range_type=range_type, min_bound=min_bound, max_bound=max_bound) + elif range_type == Ranges.Qdot: + self.qdot_ranges = RangeOfMotion(range_type=range_type, min_bound=min_bound, max_bound=max_bound) else: - raise RuntimeError(f"add_range's type must be Ranges.Q or Ranges.Qdot (you have {type})") \ No newline at end of file + raise RuntimeError(f"add_range's range_type must be Ranges.Q or Ranges.Qdot (you have {range_type})") \ No newline at end of file diff --git a/binding/python3/model_creation/segment_real.py b/binding/python3/model_creation/segment_real.py index fca3e7622..523439c82 100644 --- a/binding/python3/model_creation/segment_real.py +++ b/binding/python3/model_creation/segment_real.py @@ -2,6 +2,7 @@ from .marker_real import MarkerReal from .contact import Contact from .mesh_real import MeshReal +from .mesh_file_real import MeshFileReal from .rotations import Rotations from .range_of_motion import RangeOfMotion from .segment_coordinate_system_real import SegmentCoordinateSystemReal @@ -20,6 +21,7 @@ def __init__( qdot_ranges: RangeOfMotion = None, inertia_parameters: InertiaParametersReal = None, mesh: MeshReal = None, + mesh_file: MeshFileReal = None, ): self.name = name self.parent_name = parent_name @@ -32,6 +34,7 @@ def __init__( self.segment_coordinate_system = segment_coordinate_system self.inertia_parameters = inertia_parameters self.mesh = mesh + self.mesh_file = mesh_file def add_marker(self, marker: MarkerReal): self.markers.append(marker) @@ -43,15 +46,15 @@ def add_contact(self, contact: Contact): def __str__(self): # Define the print function, so it automatically formats things in the file properly - out_string = f"segment {self.name}\n" + out_string = f"segment\t{self.name}\n" if self.parent_name: - out_string += f"\tparent {self.parent_name}\n" + out_string += f"\tparent\t{self.parent_name}\n" if self.segment_coordinate_system: - out_string += f"\tRT {self.segment_coordinate_system}\n" + out_string += f"\tRT\t{self.segment_coordinate_system}\n" if self.translations != Translations.NONE: - out_string += f"\ttranslations {self.translations.value}\n" + out_string += f"\ttranslations\t{self.translations.value}\n" if self.rotations != Rotations.NONE: - out_string += f"\trotations {self.rotations.value}\n" + out_string += f"\trotations\t{self.rotations.value}\n" if self.q_ranges != None: out_string += str(self.q_ranges) if self.qdot_ranges != None: @@ -60,6 +63,8 @@ def __str__(self): out_string += str(self.inertia_parameters) if self.mesh: out_string += str(self.mesh) + if self.mesh_file: + out_string += str(self.mesh_file) out_string += "endsegment\n" # Also print the markers attached to the segment From 11a450a933acf756049919ae098c31264ffb0fa9 Mon Sep 17 00:00:00 2001 From: Charbie Date: Wed, 15 Jan 2025 15:14:57 +0400 Subject: [PATCH 07/10] blacked --- .../model_creation/biomechanical_model.py | 14 ++++-- .../biomechanical_model_real.py | 1 - binding/python3/model_creation/contact.py | 8 ++-- .../python3/model_creation/contact_real.py | 5 +- binding/python3/model_creation/mesh_file.py | 4 +- .../python3/model_creation/mesh_file_real.py | 29 +++++++----- binding/python3/model_creation/muscle.py | 8 +--- .../python3/model_creation/muscle_group.py | 2 +- binding/python3/model_creation/muscle_real.py | 47 ++++++++++++------- .../python3/model_creation/range_of_motion.py | 8 +--- binding/python3/model_creation/segment.py | 6 +-- binding/python3/model_creation/via_point.py | 12 ++--- .../python3/model_creation/via_point_real.py | 9 ++-- 13 files changed, 82 insertions(+), 71 deletions(-) diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 4c289188e..47652ff9e 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -81,14 +81,15 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: return model - def add_real_muscles(self, model: BiomechanicalModelReal, data: Data): for name in self.muscles: m = self.muscles[name] if m.muscle_group not in model.muscle_groups: - raise RuntimeError(f"Please create the muscle group {m.muscle_group} before putting the muscle {m.name} in it.") + raise RuntimeError( + f"Please create the muscle group {m.muscle_group} before putting the muscle {m.name} in it." + ) model.muscles[m.name] = m.to_muscle(model, data) @@ -96,16 +97,19 @@ def add_real_muscles(self, model: BiomechanicalModelReal, data: Data): vp = self.via_points[name] if vp.muscle_name not in model.muscles: - raise RuntimeError(f"Please create the muscle {vp.muscle_name} before putting the via point {vp.name} in it.") + raise RuntimeError( + f"Please create the muscle {vp.muscle_name} before putting the via point {vp.name} in it." + ) if vp.muscle_group not in model.muscle_groups: - raise RuntimeError(f"Please create the muscle group {vp.muscle_group} before putting the via point {vp.name} in it.") + raise RuntimeError( + f"Please create the muscle group {vp.muscle_group} before putting the via point {vp.name} in it." + ) model.via_points[vp.name] = vp.to_via_point(data) return model - def write(self, save_path: str, data: Data): """ Collapse the model to an actual personalized biomechanical model based on the generic model and the data diff --git a/binding/python3/model_creation/biomechanical_model_real.py b/binding/python3/model_creation/biomechanical_model_real.py index 3b257c8b3..9718b8b2e 100644 --- a/binding/python3/model_creation/biomechanical_model_real.py +++ b/binding/python3/model_creation/biomechanical_model_real.py @@ -12,7 +12,6 @@ def __init__(self): self.muscles: dict[str:MuscleReal, ...] = {} self.via_points: dict[str:ViaPointReal, ...] = {} - def __str__(self): out_string = "version 4\n\n" diff --git a/binding/python3/model_creation/contact.py b/binding/python3/model_creation/contact.py index ff16dfabd..fc95e67d0 100644 --- a/binding/python3/model_creation/contact.py +++ b/binding/python3/model_creation/contact.py @@ -4,6 +4,7 @@ from .translations import Translations from .contact_real import ContactReal + class Contact: def __init__( self, @@ -30,14 +31,11 @@ def __init__( self.parent_name = parent_name self.axis = axis - - def to_contact( - self, data: Data - ) -> ContactReal: + def to_contact(self, data: Data) -> ContactReal: return ContactReal.from_data( data, self.name, self.function, self.parent_name, self.axis, - ) \ No newline at end of file + ) diff --git a/binding/python3/model_creation/contact_real.py b/binding/python3/model_creation/contact_real.py index 34927e2d1..d60bece62 100644 --- a/binding/python3/model_creation/contact_real.py +++ b/binding/python3/model_creation/contact_real.py @@ -5,6 +5,7 @@ from .protocols import Data from .translations import Translations + class ContactReal: def __init__( self, @@ -63,8 +64,8 @@ def from_data( if not isinstance(p, np.ndarray): raise RuntimeError(f"The function {function} must return a np.ndarray of dimension 3xT (XYZ x time)") if p.shape == (3, 1): - p = p.reshape((3, )) - elif p.shape != (3, ): + p = p.reshape((3,)) + elif p.shape != (3,): raise RuntimeError(f"The function {function} must return a vector of dimension 3 (XYZ)") return ContactReal(name, parent_name, p, axis) diff --git a/binding/python3/model_creation/mesh_file.py b/binding/python3/model_creation/mesh_file.py index 9604faa82..c8d4250ca 100644 --- a/binding/python3/model_creation/mesh_file.py +++ b/binding/python3/model_creation/mesh_file.py @@ -39,9 +39,9 @@ def __init__( self.rotation_function = rotation_function self.translation_function = translation_function - def to_mesh_file( - self, data: Data, + self, + data: Data, ) -> MeshFileReal: return MeshFileReal.from_data( data, diff --git a/binding/python3/model_creation/mesh_file_real.py b/binding/python3/model_creation/mesh_file_real.py index 97ab82e23..54fec8269 100644 --- a/binding/python3/model_creation/mesh_file_real.py +++ b/binding/python3/model_creation/mesh_file_real.py @@ -8,12 +8,12 @@ class MeshFileReal: def __init__( - self, - mesh_file_name: str, - mesh_color: np.ndarray[float] | list[float] | tuple[float], - mesh_scale: np.ndarray[float] | list[float] | tuple[float], - mesh_rotation: np.ndarray[float] | list[float] | tuple[float], - mesh_translation: np.ndarray[float] | list[float] | tuple[float], + self, + mesh_file_name: str, + mesh_color: np.ndarray[float] | list[float] | tuple[float], + mesh_scale: np.ndarray[float] | list[float] | tuple[float], + mesh_rotation: np.ndarray[float] | list[float] | tuple[float], + mesh_translation: np.ndarray[float] | list[float] | tuple[float], ): """ Parameters @@ -84,32 +84,37 @@ def from_data( if mesh_scale.shape == (3, 1): mesh_scale = mesh_scale.reshape((3,)) elif mesh_scale.shape != (3,): - raise RuntimeError( - f"The scaling_function {scaling_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError(f"The scaling_function {scaling_function} must return a vector of dimension 3 (XYZ)") if rotation_function is None: mesh_rotation = np.array([0, 0, 0]) else: mesh_rotation: np.ndarray = rotation_function(data.values) if not isinstance(mesh_rotation, np.ndarray): - raise RuntimeError(f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)" + ) if mesh_rotation.shape == (3, 1): mesh_rotation = mesh_rotation.reshape((3,)) elif mesh_rotation.shape != (3,): raise RuntimeError( - f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)") + f"The rotation_function {rotation_function} must return a vector of dimension 3 (XYZ)" + ) if translation_function is None: mesh_translation = np.array([0, 0, 0]) else: mesh_translation: np.ndarray = translation_function(data.values) if not isinstance(mesh_translation, np.ndarray): - raise RuntimeError(f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)" + ) if mesh_translation.shape == (3, 1): mesh_translation = mesh_translation.reshape((3,)) elif mesh_translation.shape != (3,): raise RuntimeError( - f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)") + f"The translation_function {translation_function} must return a vector of dimension 3 (XYZ)" + ) return MeshFileReal( mesh_file_name=mesh_file_name, diff --git a/binding/python3/model_creation/muscle.py b/binding/python3/model_creation/muscle.py index c7a2f4d77..55211413b 100644 --- a/binding/python3/model_creation/muscle.py +++ b/binding/python3/model_creation/muscle.py @@ -5,7 +5,6 @@ from .muscle_real import MuscleReal, MuscleType, MuscleStateType - class Muscle: def __init__( self, @@ -21,7 +20,6 @@ def __init__( pennation_angle_function: Callable | float, maximal_excitation: float = 1, ): - """ Parameters ---------- @@ -42,9 +40,7 @@ def __init__( self.pennation_angle_function = pennation_angle_function self.maximal_excitation = maximal_excitation - def to_muscle( - self, model, data: Data - ) -> MuscleReal: + def to_muscle(self, model, data: Data) -> MuscleReal: return MuscleReal.from_data( data, model, @@ -59,4 +55,4 @@ def to_muscle( self.tendon_slack_length_function, self.pennation_angle_function, self.maximal_excitation, - ) \ No newline at end of file + ) diff --git a/binding/python3/model_creation/muscle_group.py b/binding/python3/model_creation/muscle_group.py index 8ff3c0c03..bd9cb23a3 100644 --- a/binding/python3/model_creation/muscle_group.py +++ b/binding/python3/model_creation/muscle_group.py @@ -2,6 +2,7 @@ from .protocols import Data + class MuscleGroup: def __init__( self, @@ -38,4 +39,3 @@ def __str__(self): out_string += f"\tInsertionParent\t{self.insertion_parent_name}\n" out_string += "endmusclegroup\n" return out_string - diff --git a/binding/python3/model_creation/muscle_real.py b/binding/python3/model_creation/muscle_real.py index e61dca517..278a93ab8 100644 --- a/binding/python3/model_creation/muscle_real.py +++ b/binding/python3/model_creation/muscle_real.py @@ -66,7 +66,9 @@ def __init__( self.state_type = state_type self.muscle_group = muscle_group self.origin_position = origin_position if isinstance(origin_position, np.ndarray) else np.array(origin_position) - self.insertion_position = insertion_position if isinstance(insertion_position, np.ndarray) else np.array(insertion_position) + self.insertion_position = ( + insertion_position if isinstance(insertion_position, np.ndarray) else np.array(insertion_position) + ) self.optimal_length = optimal_length self.maximal_force = maximal_force self.tendon_slack_length = tendon_slack_length @@ -74,7 +76,6 @@ def __init__( self.maximal_excitation = maximal_excitation self.via_points = via_points - @staticmethod def from_data( data: Data, @@ -89,7 +90,7 @@ def from_data( maximal_force_function: Callable | float, tendon_slack_length_function: Callable | float, pennation_angle_function: Callable | float, - maximal_excitation: float + maximal_excitation: float, ): """ This is a constructor for the Muscle class. It evaluates the function that defines the muscle to get an @@ -124,19 +125,27 @@ def from_data( """ origin_position: np.ndarray = origin_position_function(data.values) if not isinstance(origin_position, np.ndarray): - raise RuntimeError(f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)" + ) if origin_position.shape == (3, 1): origin_position = origin_position.reshape((3,)) elif origin_position.shape != (3,): - raise RuntimeError(f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The origin_position_function {origin_position_function} must return a vector of dimension 3 (XYZ)" + ) insertion_position: np.ndarray = insertion_position_function(data.values) if not isinstance(insertion_position, np.ndarray): - raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)" + ) if insertion_position.shape == (3, 1): insertion_position = insertion_position.reshape((3,)) elif insertion_position.shape != (3,): - raise RuntimeError(f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)") + raise RuntimeError( + f"The insertion_position_function {insertion_position_function} must return a vector of dimension 3 (XYZ)" + ) optimal_length: float = optimal_length_function(model, data.values) if not isinstance(optimal_length, float): @@ -154,17 +163,19 @@ def from_data( if not isinstance(pennation_angle, float): raise RuntimeError(f"The pennation_angle_function {pennation_angle_function} must return a float") - return MuscleReal(name, - muscle_type, - state_type, - muscle_group, - origin_position, - insertion_position, - optimal_length, - maximal_force, - tendon_slack_length, - pennation_angle, - maximal_excitation) + return MuscleReal( + name, + muscle_type, + state_type, + muscle_group, + origin_position, + insertion_position, + optimal_length, + maximal_force, + tendon_slack_length, + pennation_angle, + maximal_excitation, + ) def __str__(self): # Define the print function, so it automatically formats things in the file properly diff --git a/binding/python3/model_creation/range_of_motion.py b/binding/python3/model_creation/range_of_motion.py index 4c59bdb11..23e8328d6 100644 --- a/binding/python3/model_creation/range_of_motion.py +++ b/binding/python3/model_creation/range_of_motion.py @@ -1,4 +1,3 @@ - from enum import Enum @@ -8,10 +7,7 @@ class Ranges(Enum): class RangeOfMotion: - def __init__(self, - range_type: Ranges, - min_bound: list[float], - max_bound: list[float]): + def __init__(self, range_type: Ranges, min_bound: list[float], max_bound: list[float]): self.range_type = range_type self.min_bound = min_bound @@ -30,4 +26,4 @@ def __str__(self): out_string += f"\t\t{self.min_bound[i_dof]:0.5f}\t{self.max_bound[i_dof]:0.5f}\n" out_string += "\n" - return out_string \ No newline at end of file + return out_string diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 05824d0b0..9f2ba7d03 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -86,9 +86,7 @@ def add_contact(self, contact: Contact): if contact.parent_name is None: raise ValueError(f"Contacts must have parents. Contact {contact.name} does not have a parent.") elif contact.parent_name != self.name: - raise ValueError( - "The contact name should be the same as the 'key'." - ) + raise ValueError("The contact name should be the same as the 'key'.") contact.parent_name = self.name self.contacts.append(contact) @@ -106,4 +104,4 @@ def add_range(self, range_type: Ranges, min_bound, max_bound): elif range_type == Ranges.Qdot: self.qdot_ranges = RangeOfMotion(range_type=range_type, min_bound=min_bound, max_bound=max_bound) else: - raise RuntimeError(f"add_range's range_type must be Ranges.Q or Ranges.Qdot (you have {range_type})") \ No newline at end of file + raise RuntimeError(f"add_range's range_type must be Ranges.Q or Ranges.Qdot (you have {range_type})") diff --git a/binding/python3/model_creation/via_point.py b/binding/python3/model_creation/via_point.py index 11ee5f04c..914a264ab 100644 --- a/binding/python3/model_creation/via_point.py +++ b/binding/python3/model_creation/via_point.py @@ -3,6 +3,7 @@ from .protocols import Data from .via_point_real import ViaPointReal + class ViaPoint: def __init__( self, @@ -28,15 +29,14 @@ def __init__( """ self.name = name position_function = position_function if position_function is not None else self.name - self.position_function = (lambda m, bio: m[position_function]) if isinstance(position_function, str) else position_function + self.position_function = ( + (lambda m, bio: m[position_function]) if isinstance(position_function, str) else position_function + ) self.parent_name = parent_name self.muscle_name = muscle_name self.muscle_group = muscle_group - - def to_via_point( - self, data: Data - ) -> ViaPointReal: + def to_via_point(self, data: Data) -> ViaPointReal: return ViaPointReal.from_data( data, self.name, @@ -44,4 +44,4 @@ def to_via_point( self.muscle_name, self.muscle_group, self.position_function, - ) \ No newline at end of file + ) diff --git a/binding/python3/model_creation/via_point_real.py b/binding/python3/model_creation/via_point_real.py index ce7eb910f..c7cbcec2c 100644 --- a/binding/python3/model_creation/via_point_real.py +++ b/binding/python3/model_creation/via_point_real.py @@ -4,6 +4,7 @@ from .protocols import Data + class ViaPointReal: def __init__( self, @@ -67,10 +68,12 @@ def from_data( # Get the position of the contact points and do some sanity checks position: np.ndarray = position_function(data.values) if not isinstance(position, np.ndarray): - raise RuntimeError(f"The function {position_function} must return a np.ndarray of dimension 3xT (XYZ x time)") + raise RuntimeError( + f"The function {position_function} must return a np.ndarray of dimension 3xT (XYZ x time)" + ) if position.shape == (3, 1): - position = position.reshape((3, )) - elif position.shape != (3, ): + position = position.reshape((3,)) + elif position.shape != (3,): raise RuntimeError(f"The function {position_function} must return a vector of dimension 3 (XYZ)") return ViaPointReal(name, parent_name, muscle_name, muscle_group, position) From 8dc9fbd5265eece104ad450cf69784ef506509da Mon Sep 17 00:00:00 2001 From: eve-mac Date: Wed, 15 Jan 2025 19:34:01 +0400 Subject: [PATCH 08/10] made requested changes --- binding/python3/model_creation/biomechanical_model.py | 5 ----- binding/python3/model_creation/segment.py | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/binding/python3/model_creation/biomechanical_model.py b/binding/python3/model_creation/biomechanical_model.py index 47652ff9e..540b27938 100644 --- a/binding/python3/model_creation/biomechanical_model.py +++ b/binding/python3/model_creation/biomechanical_model.py @@ -79,10 +79,6 @@ def to_real(self, data: Data) -> BiomechanicalModelReal: insertion_parent_name=mg.insertion_parent_name, ) - return model - - def add_real_muscles(self, model: BiomechanicalModelReal, data: Data): - for name in self.muscles: m = self.muscles[name] @@ -123,5 +119,4 @@ def write(self, save_path: str, data: Data): The data to collapse the model from """ model = self.to_real(data) - model = self.add_real_muscles(model, data) model.write(save_path) diff --git a/binding/python3/model_creation/segment.py b/binding/python3/model_creation/segment.py index 9f2ba7d03..1e09ba21e 100644 --- a/binding/python3/model_creation/segment.py +++ b/binding/python3/model_creation/segment.py @@ -36,6 +36,12 @@ def __init__( The sequence of translation rotations The sequence of rotation + q_ranges + The range of motion of the segment + qdot_ranges + The range of motion of the segment + segment_coordinate_system + The coordinate system of the segment inertia_parameters The inertia parameters of the segment mesh From e95d80b6e4048da535866231406ff779fc1ba530 Mon Sep 17 00:00:00 2001 From: Charbie Date: Mon, 20 Jan 2025 16:42:17 +0400 Subject: [PATCH 09/10] added example + test --- examples/python3/modelCreation.py | 229 ++++++++++++++------ test/binding/Python3/test_model_creation.py | 140 ++++++------ 2 files changed, 235 insertions(+), 134 deletions(-) diff --git a/examples/python3/modelCreation.py b/examples/python3/modelCreation.py index 19a8671ae..3769c7901 100644 --- a/examples/python3/modelCreation.py +++ b/examples/python3/modelCreation.py @@ -11,22 +11,35 @@ MarkerReal, Mesh, MeshReal, + MeshFile, + MeshFileReal, Segment, SegmentReal, SegmentCoordinateSystemReal, SegmentCoordinateSystem, + Contact, + ContactReal, + MuscleGroup, + Muscle, + MuscleReal, + MuscleType, + MuscleStateType, Translations, Rotations, + RangeOfMotion, + Ranges, + ViaPoint, ) import ezc3d -from .de_leva import DeLevaTable +from de_leva import DeLevaTable #@pariterre: this version works on my env, but is is suspicious ! # # This examples shows how to # 1. Create a model from scratch using specified dimensions (model_creation_from_static) -# 1. Create a model from scratch using a template with marker names (model_creation_from_data) +# 2. Create a complex model from scratch (complex_model_from_scratch) +# 3. Create a model from scratch using a template with marker names (model_creation_from_data) # # Please note that this example will work only with the Eigen backend # @@ -45,28 +58,31 @@ def model_creation_from_static_trial(remove_temporary: bool = True): bio_model = BiomechanicalModelReal() # The trunk segment - bio_model["TRUNK"] = SegmentReal( + bio_model.segments["TRUNK"] = SegmentReal( + name="TRUNK", translations=Translations.YZ, rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, 0.53))), ) - bio_model["TRUNK"].add_marker(MarkerReal(name="PELVIS", parent_name="TRUNK")) + bio_model.segments["TRUNK"].add_marker(MarkerReal(name="PELVIS", parent_name="TRUNK")) # The head segment - bio_model["HEAD"] = SegmentReal( + bio_model.segments["HEAD"] = SegmentReal( + name="HEAD", parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( (0, 0, 0), "xyz", (0, 0, 0.53) ), mesh=MeshReal(((0, 0, 0), (0, 0, 0.24))), ) - bio_model["HEAD"].add_marker(MarkerReal(name="BOTTOM_HEAD", parent_name="HEAD", position=(0, 0, 0))) - bio_model["HEAD"].add_marker(MarkerReal(name="TOP_HEAD", parent_name="HEAD", position=(0, 0, 0.24))) - bio_model["HEAD"].add_marker(MarkerReal(name="HEAD_Z", parent_name="HEAD", position=(0, 0, 0.24))) - bio_model["HEAD"].add_marker(MarkerReal(name="HEAD_XZ", parent_name="HEAD", position=(0.24, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="BOTTOM_HEAD", parent_name="HEAD", position=(0, 0, 0))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="TOP_HEAD", parent_name="HEAD", position=(0, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="HEAD_Z", parent_name="HEAD", position=(0, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="HEAD_XZ", parent_name="HEAD", position=(0.24, 0, 0.24))) # The arm segment - bio_model["UPPER_ARM"] = SegmentReal( + bio_model.segments["UPPER_ARM"] = SegmentReal( + name="UPPER_ARM", parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( (0, 0, 0), "xyz", (0, 0, 0.53) @@ -74,11 +90,11 @@ def model_creation_from_static_trial(remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.28))), ) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER", parent_name="UPPER_ARM", position=(0, 0, 0))) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_X", parent_name="UPPER_ARM", position=(1, 0, 0))) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_XY", parent_name="UPPER_ARM", position=(1, 1, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER", parent_name="UPPER_ARM", position=(0, 0, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_X", parent_name="UPPER_ARM", position=(1, 0, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_XY", parent_name="UPPER_ARM", position=(1, 1, 0))) - bio_model["LOWER_ARM"] = SegmentReal( + bio_model.segments["LOWER_ARM"] = SegmentReal( name="LOWER_ARM", parent_name="UPPER_ARM", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -86,11 +102,11 @@ def model_creation_from_static_trial(remove_temporary: bool = True): ), mesh=MeshReal(((0, 0, 0), (0, 0, -0.27))), ) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW", parent_name="LOWER_ARM", position=(0, 0, 0))) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_Y", parent_name="LOWER_ARM", position=(0, 1, 0))) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_XY", parent_name="LOWER_ARM", position=(1, 1, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW", parent_name="LOWER_ARM", position=(0, 0, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_Y", parent_name="LOWER_ARM", position=(0, 1, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_XY", parent_name="LOWER_ARM", position=(1, 1, 0))) - bio_model["HAND"] = SegmentReal( + bio_model.segments["HAND"] = SegmentReal( name="HAND", parent_name="LOWER_ARM", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -98,24 +114,24 @@ def model_creation_from_static_trial(remove_temporary: bool = True): ), mesh=MeshReal(((0, 0, 0), (0, 0, -0.19))), ) - bio_model["HAND"].add_marker(MarkerReal(name="WRIST", parent_name="HAND", position=(0, 0, 0))) - bio_model["HAND"].add_marker(MarkerReal(name="FINGER", parent_name="HAND", position=(0, 0, -0.19))) - bio_model["HAND"].add_marker(MarkerReal(name="HAND_Y", parent_name="HAND", position=(0, 1, 0))) - bio_model["HAND"].add_marker(MarkerReal(name="HAND_YZ", parent_name="HAND", position=(0, 1, 1))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="WRIST", parent_name="HAND", position=(0, 0, 0))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="FINGER", parent_name="HAND", position=(0, 0, -0.19))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="HAND_Y", parent_name="HAND", position=(0, 1, 0))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="HAND_YZ", parent_name="HAND", position=(0, 1, 1))) # The thigh segment - bio_model["THIGH"] = SegmentReal( + bio_model.segments["THIGH"] = SegmentReal( name="THIGH", parent_name="TRUNK", rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.42))), ) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_ORIGIN", parent_name="THIGH", position=(0, 0, 0))) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_X", parent_name="THIGH", position=(1, 0, 0))) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_Y", parent_name="THIGH", position=(0, 1, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_ORIGIN", parent_name="THIGH", position=(0, 0, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_X", parent_name="THIGH", position=(1, 0, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_Y", parent_name="THIGH", position=(0, 1, 0))) # The shank segment - bio_model["SHANK"] = SegmentReal( + bio_model.segments["SHANK"] = SegmentReal( name="SHANK", parent_name="THIGH", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -124,12 +140,12 @@ def model_creation_from_static_trial(remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.43))), ) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE", parent_name="SHANK", position=(0, 0, 0))) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE_Z", parent_name="SHANK", position=(0, 0, 1))) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE_XZ", parent_name="SHANK", position=(1, 0, 1))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE", parent_name="SHANK", position=(0, 0, 0))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE_Z", parent_name="SHANK", position=(0, 0, 1))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE_XZ", parent_name="SHANK", position=(1, 0, 1))) # The foot segment - bio_model["FOOT"] = SegmentReal( + bio_model.segments["FOOT"] = SegmentReal( name="FOOT", parent_name="SHANK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -138,10 +154,10 @@ def model_creation_from_static_trial(remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, 0.25))), ) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE", parent_name="FOOT", position=(0, 0, 0))) - bio_model["FOOT"].add_marker(MarkerReal(name="TOE", parent_name="FOOT", position=(0, 0, 0.25))) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE_Z", parent_name="FOOT", position=(0, 0, 1))) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE_YZ", parent_name="FOOT", position=(0, 1, 1))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE", parent_name="FOOT", position=(0, 0, 0))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="TOE", parent_name="FOOT", position=(0, 0, 0.25))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE_Z", parent_name="FOOT", position=(0, 0, 1))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE_YZ", parent_name="FOOT", position=(0, 1, 1))) # Put the model together, print it and print it to a bioMod file bio_model.write(kinematic_model_file_path) @@ -156,6 +172,80 @@ def model_creation_from_static_trial(remove_temporary: bool = True): os.remove(kinematic_model_file_path) + +def complex_model_from_scratch(mesh_path="../test/models/meshFiles/stl/pendulum.STL", remove_temporary: bool = True): + """ + We define a new model by feeding in the actual dimension and position of the model. + Please note that this model is not a human, it is only used to show the functionalities of the model creation module. + """ + + kinematic_model_file_path = "temporary_complex.bioMod" + + # Create a model holder + bio_model = BiomechanicalModel() + + # The ground segment + bio_model.segments["GROUND"] = Segment(name="GROUND") + + # The pendulum segment + bio_model.segments["PENDULUM"] = Segment( + name="PENDULUM", + translations=Translations.XYZ, + rotations=Rotations.X, + q_ranges=RangeOfMotion(range_type=Ranges.Q, min_bound=[-1, -1, -1, -np.pi], max_bound=[1, 1, 1, np.pi]), + qdot_ranges=RangeOfMotion(range_type=Ranges.Qdot, min_bound=[-10, -10, -10, -np.pi*10], max_bound=[10, 10, 10, np.pi*10]), + mesh_file=MeshFile(mesh_file_name=mesh_path, + mesh_color=np.array([0, 0, 1]), + scaling_function=lambda m: np.array([1, 1, 10]), + rotation_function=lambda m: np.array([np.pi/2, 0, 0]), + translation_function=lambda m: np.array([0.1, 0, 0])), + ) + # The pendulum segment contact point + bio_model.segments["PENDULUM"].add_contact(Contact(name="PENDULUM_CONTACT", + function=lambda m: np.array([0, 0, 0]), + parent_name="PENDULUM", + axis=Translations.XYZ)) + + # The pendulum muscle group + bio_model.muscle_groups["PENDULUM_MUSCLE_GROUP"] = MuscleGroup(name="PENDULUM_MUSCLE_GROUP", + origin_parent_name="GROUND", + insertion_parent_name="PENDULUM") + + # The pendulum muscle + bio_model.muscles["PENDULUM_MUSCLE"] = Muscle("PENDULUM_MUSCLE", + muscle_type=MuscleType.HILLTHELEN, + state_type=MuscleStateType.DEGROOTE, + muscle_group="PENDULUM_MUSCLE_GROUP", + origin_position_function=lambda m: np.array([0, 0, 0]), + insertion_position_function=lambda m: np.array([0, 0, 1]), + optimal_length_function=lambda model, m: 0.1, + maximal_force_function=lambda m: 100.0, + tendon_slack_length_function=lambda model, m: 0.05, + pennation_angle_function=lambda model, m: 0.05, + maximal_excitation=1) + bio_model.via_points["PENDULUM_MUSCLE"] = ViaPoint("PENDULUM_MUSCLE", + position_function=lambda m: np.array([0, 0, 0.5]), + parent_name="PENDULUM", + muscle_name="PENDULUM_MUSCLE", + muscle_group="PENDULUM_MUSCLE_GROUP", + ) + + + # Put the model together, print it and print it to a bioMod file + bio_model.write(kinematic_model_file_path, {}) + + model = biorbd.Model(kinematic_model_file_path) + assert model.nbQ() == 4 + assert model.nbSegment() == 2 + assert model.nbMarkers() == 0 + assert model.nbMuscles() == 1 + assert model.nbMuscleGroups() == 1 + assert model.nbContacts() == 3 + + if remove_temporary: + os.remove(kinematic_model_file_path) + + def model_creation_from_measured_data(remove_temporary: bool = True): """ We are using the previous model to define a new model based on the position of the markers. This is solely so we @@ -192,14 +282,14 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): model = BiomechanicalModel() de_leva = DeLevaTable(total_mass=100, sex="female") - model["TRUNK"] = Segment( + model.segments["TRUNK"] = Segment( translations=Translations.YZ, rotations=Rotations.X, inertia_parameters=de_leva["TRUNK"], ) - model["TRUNK"].add_marker(Marker("PELVIS")) + model.segments["TRUNK"].add_marker(Marker("PELVIS")) - model["HEAD"] = Segment( + model.segments["HEAD"] = Segment( parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystem( "BOTTOM_HEAD", @@ -210,12 +300,12 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): mesh=Mesh(("BOTTOM_HEAD", "TOP_HEAD", "HEAD_Z", "HEAD_XZ", "BOTTOM_HEAD")), inertia_parameters=de_leva["HEAD"], ) - model["HEAD"].add_marker(Marker("BOTTOM_HEAD")) - model["HEAD"].add_marker(Marker("TOP_HEAD")) - model["HEAD"].add_marker(Marker("HEAD_Z")) - model["HEAD"].add_marker(Marker("HEAD_XZ")) + model.segments["HEAD"].add_marker(Marker("BOTTOM_HEAD")) + model.segments["HEAD"].add_marker(Marker("TOP_HEAD")) + model.segments["HEAD"].add_marker(Marker("HEAD_Z")) + model.segments["HEAD"].add_marker(Marker("HEAD_XZ")) - model["UPPER_ARM"] = Segment( + model.segments["UPPER_ARM"] = Segment( "UPPER_ARM", parent_name="TRUNK", rotations=Rotations.X, @@ -227,11 +317,11 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["UPPER_ARM"], ) - model["UPPER_ARM"].add_marker(Marker("SHOULDER")) - model["UPPER_ARM"].add_marker(Marker("SHOULDER_X")) - model["UPPER_ARM"].add_marker(Marker("SHOULDER_XY")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER_X")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER_XY")) - model["LOWER_ARM"] = Segment( + model.segments["LOWER_ARM"] = Segment( parent_name="UPPER_ARM", segment_coordinate_system=SegmentCoordinateSystem( origin="ELBOW", @@ -241,11 +331,11 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["LOWER_ARM"], ) - model["LOWER_ARM"].add_marker(Marker("ELBOW")) - model["LOWER_ARM"].add_marker(Marker("ELBOW_Y")) - model["LOWER_ARM"].add_marker(Marker("ELBOW_XY")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW_Y")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW_XY")) - model["HAND"] = Segment( + model.segments["HAND"] = Segment( parent_name="LOWER_ARM", segment_coordinate_system=SegmentCoordinateSystem( origin="WRIST", @@ -255,12 +345,12 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["HAND"], ) - model["HAND"].add_marker(Marker("WRIST")) - model["HAND"].add_marker(Marker("FINGER")) - model["HAND"].add_marker(Marker("HAND_Y")) - model["HAND"].add_marker(Marker("HAND_YZ")) + model.segments["HAND"].add_marker(Marker("WRIST")) + model.segments["HAND"].add_marker(Marker("FINGER")) + model.segments["HAND"].add_marker(Marker("HAND_Y")) + model.segments["HAND"].add_marker(Marker("HAND_YZ")) - model["THIGH"] = Segment( + model.segments["THIGH"] = Segment( parent_name="TRUNK", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -271,11 +361,11 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["THIGH"], ) - model["THIGH"].add_marker(Marker("THIGH_ORIGIN")) - model["THIGH"].add_marker(Marker("THIGH_X")) - model["THIGH"].add_marker(Marker("THIGH_Y")) + model.segments["THIGH"].add_marker(Marker("THIGH_ORIGIN")) + model.segments["THIGH"].add_marker(Marker("THIGH_X")) + model.segments["THIGH"].add_marker(Marker("THIGH_Y")) - model["SHANK"] = Segment( + model.segments["SHANK"] = Segment( parent_name="THIGH", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -286,11 +376,11 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["SHANK"], ) - model["SHANK"].add_marker(Marker("KNEE")) - model["SHANK"].add_marker(Marker("KNEE_Z")) - model["SHANK"].add_marker(Marker("KNEE_XZ")) + model.segments["SHANK"].add_marker(Marker("KNEE")) + model.segments["SHANK"].add_marker(Marker("KNEE_Z")) + model.segments["SHANK"].add_marker(Marker("KNEE_XZ")) - model["FOOT"] = Segment( + model.segments["FOOT"] = Segment( parent_name="SHANK", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -301,10 +391,10 @@ def write_markers_to_c3d(save_path: str, model: biorbd.Model): ), inertia_parameters=de_leva["FOOT"], ) - model["FOOT"].add_marker(Marker("ANKLE")) - model["FOOT"].add_marker(Marker("TOE")) - model["FOOT"].add_marker(Marker("ANKLE_Z")) - model["FOOT"].add_marker(Marker("ANKLE_YZ")) + model.segments["FOOT"].add_marker(Marker("ANKLE")) + model.segments["FOOT"].add_marker(Marker("TOE")) + model.segments["FOOT"].add_marker(Marker("ANKLE_Z")) + model.segments["FOOT"].add_marker(Marker("ANKLE_YZ")) # Put the model together, print it and print it to a bioMod file model.write(kinematic_model_file_path, C3dData(c3d_file_path)) @@ -324,6 +414,9 @@ def main(): # Create the model from user defined dimensions model_creation_from_static_trial() + # Cre a complex model from scratch + complex_model_from_scratch() + # Create the model from a data file and markers as template model_creation_from_measured_data() diff --git a/test/binding/Python3/test_model_creation.py b/test/binding/Python3/test_model_creation.py index 7ecb086c5..b825233d1 100644 --- a/test/binding/Python3/test_model_creation.py +++ b/test/binding/Python3/test_model_creation.py @@ -44,28 +44,28 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): bio_model = BiomechanicalModelReal() # The trunk segment - bio_model["TRUNK"] = SegmentReal( + bio_model.segments["TRUNK"] = SegmentReal( translations=Translations.YZ, rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, 0.53))), ) - bio_model["TRUNK"].add_marker(MarkerReal(name="PELVIS", parent_name="TRUNK")) + bio_model.segments["TRUNK"].add_marker(MarkerReal(name="PELVIS", parent_name="TRUNK")) # The head segment - bio_model["HEAD"] = SegmentReal( + bio_model.segments["HEAD"] = SegmentReal( parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( (0, 0, 0), "xyz", (0, 0, 0.53) ), mesh=MeshReal(((0, 0, 0), (0, 0, 0.24))), ) - bio_model["HEAD"].add_marker(MarkerReal(name="BOTTOM_HEAD", parent_name="HEAD", position=(0, 0, 0))) - bio_model["HEAD"].add_marker(MarkerReal(name="TOP_HEAD", parent_name="HEAD", position=(0, 0, 0.24))) - bio_model["HEAD"].add_marker(MarkerReal(name="HEAD_Z", parent_name="HEAD", position=(0, 0, 0.24))) - bio_model["HEAD"].add_marker(MarkerReal(name="HEAD_XZ", parent_name="HEAD", position=(0.24, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="BOTTOM_HEAD", parent_name="HEAD", position=(0, 0, 0))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="TOP_HEAD", parent_name="HEAD", position=(0, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="HEAD_Z", parent_name="HEAD", position=(0, 0, 0.24))) + bio_model.segments["HEAD"].add_marker(MarkerReal(name="HEAD_XZ", parent_name="HEAD", position=(0.24, 0, 0.24))) # The arm segment - bio_model["UPPER_ARM"] = SegmentReal( + bio_model.segments["UPPER_ARM"] = SegmentReal( parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( (0, 0, 0), "xyz", (0, 0, 0.53) @@ -73,11 +73,11 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.28))), ) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER", parent_name="UPPER_ARM", position=(0, 0, 0))) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_X", parent_name="UPPER_ARM", position=(1, 0, 0))) - bio_model["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_XY", parent_name="UPPER_ARM", position=(1, 1, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER", parent_name="UPPER_ARM", position=(0, 0, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_X", parent_name="UPPER_ARM", position=(1, 0, 0))) + bio_model.segments["UPPER_ARM"].add_marker(MarkerReal(name="SHOULDER_XY", parent_name="UPPER_ARM", position=(1, 1, 0))) - bio_model["LOWER_ARM"] = SegmentReal( + bio_model.segments["LOWER_ARM"] = SegmentReal( name="LOWER_ARM", parent_name="UPPER_ARM", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -85,11 +85,11 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): ), mesh=MeshReal(((0, 0, 0), (0, 0, -0.27))), ) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW", parent_name="LOWER_ARM", position=(0, 0, 0))) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_Y", parent_name="LOWER_ARM", position=(0, 1, 0))) - bio_model["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_XY", parent_name="LOWER_ARM", position=(1, 1, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW", parent_name="LOWER_ARM", position=(0, 0, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_Y", parent_name="LOWER_ARM", position=(0, 1, 0))) + bio_model.segments["LOWER_ARM"].add_marker(MarkerReal(name="ELBOW_XY", parent_name="LOWER_ARM", position=(1, 1, 0))) - bio_model["HAND"] = SegmentReal( + bio_model.segments["HAND"] = SegmentReal( name="HAND", parent_name="LOWER_ARM", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -97,24 +97,24 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): ), mesh=MeshReal(((0, 0, 0), (0, 0, -0.19))), ) - bio_model["HAND"].add_marker(MarkerReal(name="WRIST", parent_name="HAND", position=(0, 0, 0))) - bio_model["HAND"].add_marker(MarkerReal(name="FINGER", parent_name="HAND", position=(0, 0, -0.19))) - bio_model["HAND"].add_marker(MarkerReal(name="HAND_Y", parent_name="HAND", position=(0, 1, 0))) - bio_model["HAND"].add_marker(MarkerReal(name="HAND_YZ", parent_name="HAND", position=(0, 1, 1))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="WRIST", parent_name="HAND", position=(0, 0, 0))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="FINGER", parent_name="HAND", position=(0, 0, -0.19))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="HAND_Y", parent_name="HAND", position=(0, 1, 0))) + bio_model.segments["HAND"].add_marker(MarkerReal(name="HAND_YZ", parent_name="HAND", position=(0, 1, 1))) # The thigh segment - bio_model["THIGH"] = SegmentReal( + bio_model.segments["THIGH"] = SegmentReal( name="THIGH", parent_name="TRUNK", rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.42))), ) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_ORIGIN", parent_name="THIGH", position=(0, 0, 0))) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_X", parent_name="THIGH", position=(1, 0, 0))) - bio_model["THIGH"].add_marker(MarkerReal(name="THIGH_Y", parent_name="THIGH", position=(0, 1, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_ORIGIN", parent_name="THIGH", position=(0, 0, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_X", parent_name="THIGH", position=(1, 0, 0))) + bio_model.segments["THIGH"].add_marker(MarkerReal(name="THIGH_Y", parent_name="THIGH", position=(0, 1, 0))) # The shank segment - bio_model["SHANK"] = SegmentReal( + bio_model.segments["SHANK"] = SegmentReal( name="SHANK", parent_name="THIGH", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -123,12 +123,12 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, -0.43))), ) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE", parent_name="SHANK", position=(0, 0, 0))) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE_Z", parent_name="SHANK", position=(0, 0, 1))) - bio_model["SHANK"].add_marker(MarkerReal(name="KNEE_XZ", parent_name="SHANK", position=(1, 0, 1))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE", parent_name="SHANK", position=(0, 0, 0))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE_Z", parent_name="SHANK", position=(0, 0, 1))) + bio_model.segments["SHANK"].add_marker(MarkerReal(name="KNEE_XZ", parent_name="SHANK", position=(1, 0, 1))) # The foot segment - bio_model["FOOT"] = SegmentReal( + bio_model.segments["FOOT"] = SegmentReal( name="FOOT", parent_name="SHANK", segment_coordinate_system=SegmentCoordinateSystemReal.from_euler_and_translation( @@ -137,10 +137,10 @@ def test_model_creation_from_static(brbd, remove_temporary: bool = True): rotations=Rotations.X, mesh=MeshReal(((0, 0, 0), (0, 0, 0.25))), ) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE", parent_name="FOOT", position=(0, 0, 0))) - bio_model["FOOT"].add_marker(MarkerReal(name="TOE", parent_name="FOOT", position=(0, 0, 0.25))) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE_Z", parent_name="FOOT", position=(0, 0, 1))) - bio_model["FOOT"].add_marker(MarkerReal(name="ANKLE_YZ", parent_name="FOOT", position=(0, 1, 1))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE", parent_name="FOOT", position=(0, 0, 0))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="TOE", parent_name="FOOT", position=(0, 0, 0.25))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE_Z", parent_name="FOOT", position=(0, 0, 1))) + bio_model.segments["FOOT"].add_marker(MarkerReal(name="ANKLE_YZ", parent_name="FOOT", position=(0, 1, 1))) # Put the model together, print it and print it to a bioMod file bio_model.write(kinematic_model_file_path) @@ -203,14 +203,14 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): model = BiomechanicalModel() de_leva = DeLevaTable(total_mass=100, sex="female") - model["TRUNK"] = Segment( + model.segments["TRUNK"] = Segment( translations=Translations.YZ, rotations=Rotations.X, inertia_parameters=de_leva["TRUNK"], ) - model["TRUNK"].add_marker(Marker("PELVIS")) + model.segments["TRUNK"].add_marker(Marker("PELVIS")) - model["HEAD"] = Segment( + model.segments["HEAD"] = Segment( parent_name="TRUNK", segment_coordinate_system=SegmentCoordinateSystem( "BOTTOM_HEAD", @@ -221,12 +221,12 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): mesh=Mesh(("BOTTOM_HEAD", "TOP_HEAD", "HEAD_Z", "HEAD_XZ", "BOTTOM_HEAD")), inertia_parameters=de_leva["HEAD"], ) - model["HEAD"].add_marker(Marker("BOTTOM_HEAD")) - model["HEAD"].add_marker(Marker("TOP_HEAD")) - model["HEAD"].add_marker(Marker("HEAD_Z")) - model["HEAD"].add_marker(Marker("HEAD_XZ")) + model.segments["HEAD"].add_marker(Marker("BOTTOM_HEAD")) + model.segments["HEAD"].add_marker(Marker("TOP_HEAD")) + model.segments["HEAD"].add_marker(Marker("HEAD_Z")) + model.segments["HEAD"].add_marker(Marker("HEAD_XZ")) - model["UPPER_ARM"] = Segment( + model.segments["UPPER_ARM"] = Segment( "UPPER_ARM", parent_name="TRUNK", rotations=Rotations.X, @@ -238,11 +238,11 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["UPPER_ARM"], ) - model["UPPER_ARM"].add_marker(Marker("SHOULDER")) - model["UPPER_ARM"].add_marker(Marker("SHOULDER_X")) - model["UPPER_ARM"].add_marker(Marker("SHOULDER_XY")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER_X")) + model.segments["UPPER_ARM"].add_marker(Marker("SHOULDER_XY")) - model["LOWER_ARM"] = Segment( + model.segments["LOWER_ARM"] = Segment( parent_name="UPPER_ARM", segment_coordinate_system=SegmentCoordinateSystem( origin="ELBOW", @@ -252,11 +252,11 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["LOWER_ARM"], ) - model["LOWER_ARM"].add_marker(Marker("ELBOW")) - model["LOWER_ARM"].add_marker(Marker("ELBOW_Y")) - model["LOWER_ARM"].add_marker(Marker("ELBOW_XY")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW_Y")) + model.segments["LOWER_ARM"].add_marker(Marker("ELBOW_XY")) - model["HAND"] = Segment( + model.segments["HAND"] = Segment( parent_name="LOWER_ARM", segment_coordinate_system=SegmentCoordinateSystem( origin="WRIST", @@ -266,12 +266,12 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["HAND"], ) - model["HAND"].add_marker(Marker("WRIST")) - model["HAND"].add_marker(Marker("FINGER")) - model["HAND"].add_marker(Marker("HAND_Y")) - model["HAND"].add_marker(Marker("HAND_YZ")) + model.segments["HAND"].add_marker(Marker("WRIST")) + model.segments["HAND"].add_marker(Marker("FINGER")) + model.segments["HAND"].add_marker(Marker("HAND_Y")) + model.segments["HAND"].add_marker(Marker("HAND_YZ")) - model["THIGH"] = Segment( + model.segments["THIGH"] = Segment( parent_name="TRUNK", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -282,11 +282,11 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["THIGH"], ) - model["THIGH"].add_marker(Marker("THIGH_ORIGIN")) - model["THIGH"].add_marker(Marker("THIGH_X")) - model["THIGH"].add_marker(Marker("THIGH_Y")) + model.segments["THIGH"].add_marker(Marker("THIGH_ORIGIN")) + model.segments["THIGH"].add_marker(Marker("THIGH_X")) + model.segments["THIGH"].add_marker(Marker("THIGH_Y")) - model["SHANK"] = Segment( + model.segments["SHANK"] = Segment( parent_name="THIGH", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -297,11 +297,11 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["SHANK"], ) - model["SHANK"].add_marker(Marker("KNEE")) - model["SHANK"].add_marker(Marker("KNEE_Z")) - model["SHANK"].add_marker(Marker("KNEE_XZ")) + model.segments["SHANK"].add_marker(Marker("KNEE")) + model.segments["SHANK"].add_marker(Marker("KNEE_Z")) + model.segments["SHANK"].add_marker(Marker("KNEE_XZ")) - model["FOOT"] = Segment( + model.segments["FOOT"] = Segment( parent_name="SHANK", rotations=Rotations.X, segment_coordinate_system=SegmentCoordinateSystem( @@ -312,10 +312,10 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): ), inertia_parameters=de_leva["FOOT"], ) - model["FOOT"].add_marker(Marker("ANKLE")) - model["FOOT"].add_marker(Marker("TOE")) - model["FOOT"].add_marker(Marker("ANKLE_Z")) - model["FOOT"].add_marker(Marker("ANKLE_YZ")) + model.segments["FOOT"].add_marker(Marker("ANKLE")) + model.segments["FOOT"].add_marker(Marker("TOE")) + model.segments["FOOT"].add_marker(Marker("ANKLE_Z")) + model.segments["FOOT"].add_marker(Marker("ANKLE_YZ")) # Put the model together, print it and print it to a bioMod file model.write(kinematic_model_file_path, C3dData(c3d_file_path)) @@ -329,3 +329,11 @@ def test_model_creation_from_data(brbd, remove_temporary: bool = True): if remove_temporary: os.remove(kinematic_model_file_path) os.remove(c3d_file_path) + + + +def test_complex_model(): + from examples.python3 import modelCreation + model_creation_folder = os.path.dirname(modelCreation.__file__) + mesh_path = model_creation_folder + "/../../test/models/meshFiles/stl/pendulum.STL" + modelCreation.complex_model_from_scratch(mesh_path=mesh_path) \ No newline at end of file From c6062f0a75aa6a61b6abcbf334e1bd6d2d4966bd Mon Sep 17 00:00:00 2001 From: Charbie Date: Fri, 24 Jan 2025 08:54:43 +0400 Subject: [PATCH 10/10] change requested by pariterre --- binding/python3/model_creation/segment_real.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/python3/model_creation/segment_real.py b/binding/python3/model_creation/segment_real.py index 523439c82..e8753624d 100644 --- a/binding/python3/model_creation/segment_real.py +++ b/binding/python3/model_creation/segment_real.py @@ -55,9 +55,9 @@ def __str__(self): out_string += f"\ttranslations\t{self.translations.value}\n" if self.rotations != Rotations.NONE: out_string += f"\trotations\t{self.rotations.value}\n" - if self.q_ranges != None: + if self.q_ranges is not None: out_string += str(self.q_ranges) - if self.qdot_ranges != None: + if self.qdot_ranges is not None: out_string += str(self.qdot_ranges) if self.inertia_parameters: out_string += str(self.inertia_parameters)