bionc/protocols/biomechanical_model_markers.py

Summary

Maintainability
A
0 mins
Test Coverage
from abc import ABC, abstractmethod

import numpy as np
from casadi import MX

from .biomechanical_model_segments import GenericBiomechanicalModelSegments
from .natural_coordinates import NaturalCoordinates


class GenericBiomechanicalModelMarkers(ABC):
    """
    This is an abstract base class that provides the basic structure and methods for all markers of a biomechanical models.
    The implemented methods are not specific to numpy or casadi.

    Attributes
    ----------
    segments : dict
        A dictionary containing the segments of the model. The keys are the names of the segments and the values are the corresponding segment objects.

    Methods
    -------
    nb_markers(self) -> int
        Returns the number of markers in the model.
    nb_markers_technical(self) -> int
        Returns the number of technical markers in the model.
    marker_names(self) -> list[str]
        Returns the names of the markers in the model.
    marker_names_technical(self) -> list[str]
        Returns the names of the technical markers in the model.
    marker_technical_index
        This function returns the index of the marker with the given name
    markers(self, Q: NaturalCoordinates)
        Returns the position of the markers of the system as a function of the natural coordinates Q.
    constraints(self, markers: np.ndarray | MX, Q: NaturalCoordinates)
        Returns the marker constraints of all segments.
    constraints_jacobian(self)
        Returns the Jacobian matrix the markers constraints.
    center_of_mass_position(self, Q: NaturalCoordinates)
        Returns the position of the center of mass of each segment as a function of the natural coordinates Q.
    Q_from_markers(self, markers: np.ndarray) -> NaturalCoordinates
        Returns the natural coordinates of the system as a function of the markers positions.
    """

    def __init__(
        self,
        segments: GenericBiomechanicalModelSegments = None,
    ):
        self.segments = segments

    @property
    def nb_markers(self) -> int:
        """
        This function returns the number of markers in the model
        """
        nb_markers = 0
        for key in self.segments.segments_no_ground:
            nb_markers += self.segments[key].nb_markers
        return nb_markers

    @property
    def nb_markers_technical(self) -> int:
        """
        This function returns the number of technical markers in the model
        """
        nb_markers = 0
        for key in self.segments.segments_no_ground:
            nb_markers += self.segments[key].nb_markers_technical
        return nb_markers

    @property
    def names(self) -> list[str]:
        """
        This function returns the names of the markers in the model
        """
        marker_names = []
        for key in self.segments.segments_no_ground:
            marker_names += self.segments[key].marker_names
        return marker_names

    @property
    def names_technical(self) -> list[str]:
        """
        This function returns the names of the technical markers in the model
        """
        marker_names = []
        for key in self.segments.segments_no_ground:
            marker_names += self.segments[key].marker_names_technical
        return marker_names

    def marker_technical_index(self, name: str) -> int:
        """
        This function returns the index of the marker with the given name

        Parameters
        ----------
        name : str
            The name of the marker

        Returns
        -------
        int
            The index of the marker with the given name
        """
        return self.names_technical.index(name)

    def indexes(self, segment_index: int, only_technical) -> slice:
        """
        This function returns the indexes of the markers of the given segment

        Parameters
        ----------
        segment_index: int
            The index of the segment
        only_technical:
            If True, only the technical markers are considered

        Returns
        -------
        slice
            The indexes of the markers of the given segment
        """
        marker_count = 0
        for i_segment, segment in enumerate(self.segments.segments_no_ground.values()):
            nb_segment_markers = segment.nb_markers_technical if only_technical else segment.nb_markers

            if i_segment == segment_index:
                break

            marker_count += nb_segment_markers

        return slice(marker_count, marker_count + nb_segment_markers)

    def direction_index(self, segment_index: int, only_technical) -> slice:
        """
        This function returns the direction indexes (x,y,z) of the markers of the given segment

        Parameters
        ----------
        segment_index: int
            The index of the segment
        only_technical:
            If True, only the technical markers are considered

        Returns
        -------
        slice
            The indexes of the markers of the given segment
        """
        start = self.indexes(segment_index, only_technical).start
        stop = self.indexes(segment_index, only_technical).stop

        return slice(start * 3, stop * 3)

    @abstractmethod
    def markers(self, Q: NaturalCoordinates):
        """
        This function returns the position of the markers of the system as a function of the natural coordinates Q
        also referred as forward kinematics

        Parameters
        ----------
        Q : NaturalCoordinates
            The natural coordinates of the segment [12 x n, 1]

        Returns
        -------
            The position of the markers [3, nbMarkers, nbFrames]
            in the global coordinate system/ inertial coordinate system
        """

    @abstractmethod
    def constraints(self, markers: np.ndarray | MX, Q: NaturalCoordinates, only_technical: bool):
        """
        This function returns the marker constraints of all segments, denoted Phi_r
        as a function of the natural coordinates Q.

        markers : np.ndarray | MX
            The markers positions [3, nb_markers]
        Q : NaturalCoordinates
            The natural coordinates of the segment [12 * nb_segments, 1]
        only_technical : bool
            If True, only the technical markers are considered

        Returns
        -------
            Defects of the marker constraints [nb_markers x 3, 1]
        """

    @abstractmethod
    def constraints_xyz(self, markers: np.ndarray | MX, Q: NaturalCoordinates, only_technical: bool):
        """
        This function returns the marker constraints of all segments, denoted Phi_r
        as a function of the natural coordinates Q, in pyomeca format [3, nb_markers].

        markers : np.ndarray | MX
            The markers positions [3, nb_markers]
        Q : NaturalCoordinates
            The natural coordinates of the segment [12 * nb_segments, 1]
        only_technical : bool
            If True, only the technical markers are considered

        Returns
        -------
            Defects of the marker constraints [3, nb_markers]
        """

    @abstractmethod
    def constraints_jacobian(self, only_technical: bool):
        """
        This function returns the Jacobian matrix the markers constraints, denoted k_m.

        only_technical : bool
            If True, only technical markers are considered, by default True,
            because we only want to use technical markers for inverse kinematics, this choice can be revised.

        Returns
        -------
            Jacobian of the constraints of the marker [nb_markers x 3, nb_Q]
        """

    @abstractmethod
    def center_of_mass_position(self, Q: NaturalCoordinates):
        """
        This function returns the position of the center of mass of each segment as a function of the natural coordinates Q

        Parameters
        ----------
        Q : NaturalCoordinates
            The natural coordinates of the segment [12 x n, 1]

        Returns
        -------
            The position of the center of mass [3, nbSegments]
            in the global coordinate system/ inertial coordinate system
        """

    @abstractmethod
    def Q_from_markers(self, markers: np.ndarray) -> NaturalCoordinates:
        """
        This function returns the natural coordinates of the system as a function of the markers positions
        also referred as inverse kinematics
        but the constraints are not enforced,
        this can be used as an initial guess for proper inverse kinematics.

        Parameters
        ----------
        markers : np.ndarray
            The markers positions [3, nb_markers]

        Returns
        -------
        NaturalCoordinates
            The natural coordinates of the segment [12 x n, 1]

        See Also
        --------
        ..bionc_numpy.inverse_kinematics
        """