Code Monkey home page Code Monkey logo

bionc's Introduction

Bio

Pierre Puchaud, Ph.D, Postdoctoral Researcher @Université de Montréal, Laboratoire de Simulation et Modélisation du Mouvement (S2M) (he/him)

I am a postdoctoral researcher at Université of Montréal inside the S2M Lab working with Mickael Begon on biomechanics and predictive simulation. Before that, I was a Ph.D student at Ecole Normale Superieure de Rennes, with Charles Pontonnier and Georges Dumont. I am passionate about understanding and modeling human movements. Thanks to my Ph.D in biomechanics, I want to address scientific questions such as the muscle forces, fatigue modelling, population-based biomechanics, optimal design of assistive devices, and anatomical joint modelling.

EDUCATION 🎓

  • Ph.D in Biomechanics, Ecole Normale Supérieure de Rennes, France (2017-2020)
  • M.Sc in Biomechanics, Ecole Nationale Supérieure des Arts et Métiers, Paris, France (2016-2017)
  • Engineer in Mechanical Engineering, Ecole Nationale Supérieure de Mécanique et des Microtechniques, Besançon, France (2013-2016)

Interests in short

  • Biomechanics, Optimization, Motion Simulation, Joint modeling, Muscle modeling, Multibody Dynamics

Main Coding Projects

Project Language Description GitHub
Bioptim Python A Python library for biomechanics optimal control, integrating Acados and Ipopt solvers, with my leadership in theoretical developments and coordination. Published in IEEE Transactions. GitHub
CusToM MATLAB Contributed to a MATLAB toolbox for musculoskeletal analyses and model personalization during my PhD, with user customization and modularity. Published in the Journal of Open Source Software. GitHub
BioNC Python Initiated and led the development of BioNC for biomechanics dynamics in natural coordinates, facilitating model creation and analysis. GitHub
CocoFEST Python Functional electric stimulation optimal control, based on Bioptim framework, addressing dynamic modeling and fatigue behavior. More to come with @kev1co. GitHub
Majority-judgment Python and Rust Led an initiative to follow public opinion, according to majority judgment, an alternative voting method, during the French presidential campaign of 2022. I collected data from public opinion polls and presented monthly developments to the association members. More recently, I led the implementation of the voting system in Rust Language. Link1, Link2

Recent Talks

ECCOMAS Thematic Conference on Multibody Dynamics (July 24-28, 2023, Lisbon, Portugal) - ACCEPTED

  • Puchaud, P., Dumas, R., & Begon, M. (2023). Exploring the Benefits of Variational Integrators with Natural Coordinates: A Pendulum Example. Code
  • Puchaud, P., Charbonneau, E., & Begon, M. (2023). Direct multiple shooting and direct collocation optimal control for acrobatics. Code

Canadian Society for Mechanical Engineering (CSME) International Congress 2023 (May 28-31, 2023, Sherbrooke, QC, Canada). - ACCEPTED

  • Puchaud, P., Michaud, B., & Begon, M. (2023). Simulating Human Motion with Fatigue Dynamics: An Adaptation of Xia's Three-Compartment Model. Code

  • WORKSHOP: Advanced Musculoskeletal Biomechanics with Optimal Control: An Introduction to Bioptim, join the event

Representative Publications

Puchaud, P., Michaud, B., & Begon, M. (2024). The interplay of fatigue dynamics and task achievement using optimal control predictive simulation Code, 🐦Twitter Thread, Preprint, Paper accepted in Human Movement Science.

Puchaud, P., Bailly, F., & Begon, M. (2023). Direct multiple shooting and direct collocation perform similarly in biomechanical predictive simulations. Computer Methods in Applied Mechanics and Engineering, 414, 116162. Code, Preprint, Paper,

Puchaud, P., Charbonneau, E., Michaud, B., & Begon, M. (2023). Optimality equivalence and computational advantages of free-floating base dynamics compared to full-body dynamics. Mechanism and Machine Theory, 181, 105164. Code, Database, 🐦Twitter Thread, Preprint, Paper

Puchaud, P., Sauret, C., Muller, A., Bideau, N., Dumont, G., Pillet, H., & Pontonnier, C. (2020). Accuracy and kinematics consistency of marker-based scaling approaches on a lower limb model: a comparative study with imagery data. Computer Methods in Biomechanics and Biomedical Engineering, 23(3), 114-125. Accepted Version

• Hybois, S., Puchaud, P., Bourgain, M., Lombart, A., Bascou, J., Lavaste, F., ... & Sauret, C. (2019). Comparison of shoulder kinematic chain models and their influence on kinematics and kinetics in the study of manual wheelchair propulsion. Medical Engineering & Physics, 69, 153-160. Accepted Version

Languages and Tools:

Python, Matlab, LaTeX, Git, and a little bit of C++ and Rust.

Github Statistics:

Ipuch's GitHub stats [Top Langs]

bionc's People

Contributors

aaiaueil avatar anaaim avatar ipuch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bionc's Issues

Inverse Dynamics

Three versions has to be considered:

  • no muscles, no articulation constraints, intersegmental forces express in global
  • no muscles, no articulation constraints, intersegmental forces express in euler basis (WIP) #74
  • no muscles, with articular constraints, project, and rigid constraint null space
  • with muscles, requires optimization.

Docstrings

Make the docstrings of Hinge and Spherical joints as for Universal joints

transformation matrix from natural segment

from enum import Enum

class TransformationMatrix(Enum):
VW = "VW"
UV = "UV"
WU = "WU"

if transformmation_matrix == TransformationMatrix.VW:
# do something
elif transformmation_matrix == TransformationMatrix.UV:
# do something
elif transformmation_matrix == TransformationMatrix.WU:
# do something

Question about new motor constraint.

Hey,

As i have a gold fish memory, i do not recall where is the example of a new constraint in your code (as we are trying to implement new motor constraint with a PhD student) Is it in test_utlimate_inverse_kinematics ?

Thanks :)

Refactoring Inertial Parameters & Improvements Needed

While working on refactoring the inertial parameters, I've realized and acted upon some inadequacies in how I initially coded the segments. After addressing concerns raised by @ANaaim in #96, I've managed to improve the code. Here's a brief of what I've done and what's still pending.

I've added methods to easily set inertial parameters at the segment level in #99
Example:

biomechmodel["thigh"].set_inertial_parameters(mass=..., center_of_mass=..., inertia_matrix=..., transformation_matrix_type=...)

Pending Tasks:

  • Implement methods for the entire biomech model, e.g., Biomech.set_inertia_parameters(segment_name=..., ...). This method should also update the mass matrix.
  • Create links between the model creator and these new methods to allow direct value specification.
  • Introduce functions related to inertial parameters (from anthropometric tables) that can auto-adjust based on segment length during model creation. This will eliminate the need to manually input these values.

How to offer test on ''real'' c3d data ?

I am aware that c3d data should not be in a git repository : Should we still offer some real c3d data in a outside repertory from which user could download and that they would have to add themselves to a specific repertory ?

==> I think it would definitely allow to offer ''meningfull'' example.

We could use data already available on a open data-set (for example : https://www.nature.com/articles/s41597-019-0124-4)

Any function to calculate automatically Qdot and Qddot from an experimental Q (or post optim) ?

Hello,

I do not found any function to automatically calculate naturalVelocities and naturalAccelerations from a naturalsegment (from experimental data). Is there any ? I might have not looked properly.

If not might be interesting for beginner user in order to have them only to give the Q extracted in the inverse function. In that case we would calculate ourselve the qdot and qddot.

Best regards

Add a function to export the natural coordinate and associated value in a c3d files

This c3d should contain :

  • Everything that was in the initial c3d
  • The Q value after the optimisation process
  • The technical marker associated with the Q post optim
  • Kinematics calcultad for all the different joint in the model.
  • Maybe in the metadata all the additional information on the joint used in the model, any parameters

Different aim in this function :

  • Could replace the visualisation in bioviz for controlling data and using any c3d reader such as Mokka.
  • Have a file that could be analysed without using bioNC for example for people not very used to using code it would only be needed to program them a c3d reader to extract the data.

(That it is clearly inspired fro what it is done in any gait analysis software).

Any comment, comment, or opignon on this ?

Multiple Initial Guess Modes for InverseKinematics

Current Behavior:
The default behavior of the InverseKinematics class aims to compute initial guesses for joint angles using marker data. The approach relies on a function defined by the model_creator from the c3d module.

This function is invoked internally using the method model.Q_from_markers, which approximates joint angles Q based on the marker positions. An optimization process further refines these initial guesses to ensure that they adhere to specified constraints.

self.model.Q_from_markers(self.experimental_markers[:, :, :])

For users, the interface is kept simple and does not require any manual initialization for the initial guesses:

ik_solver = InverseKinematics(
        model, markers, solve_frame_per_frame=True, 
        active_direct_frame_constraints=False, use_sx=True, Q_init=None
    )
Qopt_ipopt = ik_solver.solve(method="ipopt")

Proposed Enhancement:
To provide more flexibility, we can introduce multiple modes for initial guesses. I propose the following initial guess modes:

  • Experimental:
    Usage: InverseKinematics(..., initial_guess_mode=InitialGuessMode.FROM_CURRENT_MARKERS)
    Description: This is the default behavior. It uses the experimental marker data to form initial guesses. No Q_init input is needed.
  • Experimental First Frame:
    Usage: InverseKinematics(..., initial_guess_mode=InitialGuessMode.FROM_FIRST_FRAME_MARKERS)
    Description: This mode takes the experimental data from the first frame as the initial guess. This guess is then used as a starting point or "warm start" for subsequent frame optimizations. This approach is iterative, refining the guess with each frame until the entire sequence is processed. No Q_init is required here either.
  • Custom:
    Usage: InverseKinematics(..., initial_guess_mode=InitialGuessMode.USER_PROVIDED)
    Description: In this mode, users can provide their custom initial guesses for joint angles with the Q_init argument.

Here is the Enum to implement:

class InitialGuessMode(Enum):
    FROM_CURRENT_MARKERS = "From Current Markers"
    FROM_FIRST_FRAME_MARKERS = "From First Frame Markers"
    USER_PROVIDED = "User Provided"

Referring to the email discussion we had this summer @anmuller :)

Joints implementation

Minimum Joints need to be implemented to handle articulated kinematic chains. A draft has been begun but it needs to interact with the BioemchanicalModel

class Joint:
"""
The public interface to the different Joint classes
"""
class Hinge(JointBase):
"""
This joint is defined by 3 constraints to pivot around a given axis defined by two angles theta_1 and theta_2.
"""
def __init__(
self,
joint_name: str,
segment_parent: NaturalSegment,
segment_child: NaturalSegment,
theta_1: float,
theta_2: float,
):
super(Joint.Hinge, self).__init__(joint_name, segment_parent, segment_child)
self.theta_1 = theta_1
self.theta_2 = theta_2
def constraint(self, Q_parent: SegmentNaturalCoordinates, Q_child: SegmentNaturalCoordinates) -> np.ndarray:
"""
This function returns the kinematic constraints of the joint, denoted Phi_k
as a function of the natural coordinates Q_parent and Q_child.
Returns
-------
np.ndarray
Kinematic constraints of the joint [3, 1]
"""
constraint = np.zeros(3)
constraint[0] = Q_parent.rd - Q_child.rp
constraint[1] = np.dot(Q_parent.w, Q_child.rp - Q_child.rd) - self.segment_child.length * np.cos(
self.theta_1
)
constraint[2] = np.dot(Q_parent.w, Q_child.u) - np.cos(self.theta_2)
return constraint
class Universal(JointBase, ABC):
def __init__(
self,
joint_name: str,
segment_parent: NaturalSegment,
segment_child: NaturalSegment,
theta: float,
):
super(Joint.Universal, self).__init__(joint_name, segment_parent, segment_child)
self.theta = theta
def constraint(self, Q_parent: SegmentNaturalCoordinates, Q_child: SegmentNaturalCoordinates) -> np.ndarray:
"""
This function returns the kinematic constraints of the joint, denoted Phi_k
as a function of the natural coordinates Q_parent and Q_child.
Returns
-------
np.ndarray
Kinematic constraints of the joint [2, 1]
"""
N = np.zeros((3, 12)) # interpolation matrix of the given axis
constraint = np.zeros(2)
constraint[0] = Q_parent.rd - Q_child.rp
constraint[1] = np.dot(Q_parent.w, np.matmul(N, Q_child.vector)) - np.cos(self.theta)
return constraint
class Spherical(JointBase):
def __init__(
self,
joint_name: str,
segment_parent: NaturalSegment,
segment_child: NaturalSegment,
point_interpolation_matrix_in_child: float = None,
):
super(Joint.Spherical, self).__init__(joint_name, segment_parent, segment_child)
# todo: do something better
# this thing is not none if the joint is not located at rp nor at rd and it needs to be used
self.point_interpolation_matrix_in_child = point_interpolation_matrix_in_child
def constraint(self, Q_parent: SegmentNaturalCoordinates, Q_child: SegmentNaturalCoordinates) -> np.ndarray:
"""
This function returns the kinematic constraints of the joint, denoted Phi_k
as a function of the natural coordinates Q_parent and Q_child.
Returns
-------
np.ndarray
Kinematic constraints of the joint [2, 1]
"""
N = np.zeros((3, 12)) # interpolation matrix of the given axis
constraint = np.zeros(1)
if self.point_interpolation_matrix_in_child is None:
constraint[0] = Q_parent.rd - Q_child.rp
else:
constraint[0] = np.matmul(self.point_interpolation_matrix_in_child, Q_parent.vector) - Q_child.rd
return constraint

All functions and methods need to be in snake case

To build a python package, we need to respect some standards:

Every methods needs to be in snake case, for example:

def rigidBodyConstraint(...)

should be changed into:

def rigidbody_constraint(...)

Pendulum simulation

  • 1. Joints with the grounds

  • 2. Forward dynamics with biomechanical model O(n3) see if (On) is easy to implement.
    https://youtu.be/tBGkfLL2Vs8

  • 2.2 Pendulum without forces

  • 3.1 external forces and torque applied on any point for any segment.

  • 3.2 Internal generalized forces along joint axis (WIP)) #74

Ground joints

need to implement more joints with the ground. Only hinge, cardan and spherical joints are implemented

Tests all the class

  • natural segment
  • segment
  • biomechanical model
  • templates for model creation from c3d

Generalized constraints

class Constraints:
     class ConstantDistance:

               def    constraint()
               def    cosntraint_jacobian()


     class ConstantAngle:

inertia parameter idea

ip = SegmentInertiaParameter.from_scs(mass, ...)
ip = SegmentInertiaParameter.from_ncs(mass, ...)
mass, center_of_mass, J = ip.to_natural_system()
ip_thigh = Dumas.Thigh

Also, they should be given from natural coordinate system in the class ̀NaturalSegment`

kwarg for add_joint entries.

following #78

We should consider a kwarg to pass any entry to the joint:

def add_joint(
self,
name: str,
joint_type: JointType,
parent: str,
child: str,
parent_axis: NaturalAxis | tuple[NaturalAxis] | list[NaturalAxis] = None,
child_axis: NaturalAxis | tuple[NaturalAxis] | list[NaturalAxis] = None,
parent_point: str = None,
child_point: str = None,
length: float = None,
theta: float | tuple[float] | list[float] = None,
):
"""
This method adds a joint to the model
Parameters
----------
name: str
The name of the joint
joint_type : JointType
The joint to add
parent : str
The name of the parent segment
child : str
The name of the child segment
parent_axis : NaturalAxis | tuple[NaturalAxis] | list[NaturalAxis]
The axis of the parent segment, zero, one or two element but not more.
child_axis : NaturalAxis | tuple[NaturalAxis] | list[NaturalAxis]
The axis of the child segment, zero, one or two element but not more.
parent_point : str
The name of the parent point
child_point : str
The name of the child point
length : float
The length for the constant length joint constraint
theta : float | tuple[float] | list[float]
The angle of axis constraints, zero, one or two element but not more.
Returns
-------
None
"""
if name is None:
name = f"{parent}_{child}"
self.joints[name] = dict(
name=name,
joint_type=joint_type,
parent=parent,
child=child,
parent_axis=parent_axis,
child_axis=child_axis,
theta=theta,
parent_point=parent_point,
child_point=child_point,
length=length,
)

documentation of NaturalSegment need to be up to date

NaturalSegment docstrings are not up to date, each attribute and method need to be listed.

class NaturalSegment:
"""
Class used to define anatomical segment based on natural coordinate.
Methods
-------
transformation_matrix()
This function returns the transformation matrix, denoted Bi
rigidBodyConstraint()
This function returns the rigid body constraints of the segment, denoted phi_r
rigidBodyConstraintJacobian()
This function returns the jacobian of rigid body constraints of the segment, denoted K_r
Attributes
----------
_name : str
name of the segment
_length : float
length of the segment
_alpha : float
angle between u and w
_beta : float
angle between w and (rp-rd)
_gamma : float
angle between (rp-rd) and u
_mass : float
mass of the segment in Segment Coordinate System
_center_of_mass : np.ndarray
center of mass of the segment in Segment Coordinate System
_inertia: np.ndarray
inertia matrix of the segment in Segment Coordinate System
"""

Adaptation of the Joint Base Class to integrate parent_point and child_point (eventually ?)

After the #85 the following was discussed :

"Super" calls the parent class JointBase that has common entries for every joint. As all joints doesn't have a parent and child point yet.
It's not relevant to initialize them now. But this would be a great idea in the future !
Then, to initialize parent and child point with proximal and distal point would be automated for every joint, in the parent class JointBase to avoid redundancy.

Option in model.add_joint with JointType.SPHERICAL

In the following adding of a joint is it possible to add the option ground_application point to be able to construct a spherical joint not exclusively at the rd point of the proximal segment ?
to have :

    model.add_joint(
        name="left_hip",
        joint_type=JointType.SPHERICAL,
        ground_application_point=left_hip_joint,
        parent="PELVIS",
        child="LTHIGH",)

Instead of this :

    model.add_joint(
        name="left_hip",
        joint_type=JointType.SPHERICAL,
        parent="PELVIS",
        child="LTHIGH",)

But maybe I missed something (i have adapted the function in the repertory bionc \examples\model_creation to test it on true data and see the results)

No mass_matrix creation when using SegmentTemplate.

When creating a segment in a model using the following :

    model["RSHANK"] = SegmentTemplate(
        natural_segment=NaturalSegmentTemplate(
            u_axis=AxisFunctionTemplate(
                function=lambda m, bio: MarkerTemplate.normal_to(m, bio, right_knee_joint(m, bio), "RFAL", "RTAM")
            ),
            proximal_point=right_knee_joint,
            # the knee joint computed from the medial femoral epicondyle and the lateral femoral epicondyle
            distal_point=lambda m, bio: MarkerTemplate.middle_of(m, bio, "RFAL", "RTAM"),
            w_axis=AxisTemplate(start="RTAM", end="RFAL"),
        ),
        inertia_parameters=InertiaParametersTemplate(mass=1,
            center_of_mass=np.array([0, -0.5, 0]),  # in segment coordinates system
            inertia=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])

    )

The inertia parameters are not given to the natural segment later on. I have the following error later on in the code (in inverse_dynamic class):

  File "----------------------------------------\bioNC\bioNC\bionc\bionc_casadi\natural_segment.py", line 768, in inverse_dynamics
    (self.mass_matrix @ Qddoti)
     ~~~~~~~~~~~~~~~~~^~~~~~~~

because self.mass_matrix is None.

Should the segment constructed differently or a specific command should be run for the segmentTemplate to update the different value ? I saw that for the examples three_link_pendulum.py, you are directly creating natural segment with the inertia properties which is different from creating from c3d data i suppose.

I will continue to investigate ... 🕵️

Thanks

Correct some docstring according to the following template

        """
        Compute the normalized cross product between two vectors

        Parameters
        ----------
        m: dict[str, float]
            Dictionnaries containing the location of markers in global frames
        bio: BiomechanicalModel
            The model as it is constructed at that particular time. It is useful if some values must be obtained from previously computed values
        v1: np.ndarray
            First vector
        v2: np.ndarray
            Second vector

        Returns
        -------
        normalized_vector : np.ndarray
        """

Some thoughts

  • when integrating natural coordinates we have to make sure the u and w axis keep their norm equal to 1, like when we integrate quaternions.

  • question: when actuating joints, do we have to care about the constraints? Can we actuate the model along the free dofs of joints ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.