Source code for seqikpy.body_config

import logging
import numpy as np
from copy import deepcopy as _deepcopy
from typing import Optional

_logger = logging.getLogger(__name__)


[docs] class BodyConfig: segment_sizes: dict[str, float] points_to_align: dict[str, list[str]] skeleton: list[str] template: dict[str, np.ndarray] _initial_angles_rad: Optional[dict[str, dict[str, np.ndarray]]] _dof_bounds_rad: Optional[dict[str, tuple[float, float]]] def __init__( self, segment_sizes: dict[str, float], points_to_align: dict[str, list[str]], skeleton: list[str], template: dict[str, np.ndarray], initial_angles_rad: Optional[dict[str, dict[str, np.ndarray]]] = None, initial_angles_deg: Optional[dict[str, dict[str, np.ndarray]]] = None, dof_bounds_rad: Optional[dict[str, tuple[float, float]]] = None, dof_bounds_deg: Optional[dict[str, tuple[float, float]]] = None, ): """ Configuration for the body model used in physics simulation. Attributes: segment_sizes (dict[str, float]): Size of the template body segments. skeleton (list[str]): Key points that are used in alignment process. points_to_align (dict[str, list[str]]): Body key points to align, to be provided in `AlignPose`. Each key is the name of a kinematic chain, and each value is a list of body keypoint names. All body keypoint name must be found in `skeleton`. template (dict[str, np.ndarray]): A template for the specified skeleton, i.e. the xyz position of all body keypoints. For example, this can be the pose of each body landmark in the NeuroMechFly v0.0.6 model. Note that each leg segment represents the joint in the proximal part For example, RF_Coxa means Thorax-Coxa joint initial_angles_rad (Optional[dict[str, dict[str, np.ndarray]]]): Initial joint angles for each leg and stage, in radians. The keys of the outer dict are leg names (e.g., "RF", "LF"), and the keys of the inner dict are stage names (e.g., "stage_1", "stage_2"). The values of the inner dict are numpy arrays of joint angles relevant to that processing stage in radians. initial_angles_deg (Optional[dict[str, dict[str, np.ndarray]]]): Same as `initial_angles_rad`, but in degrees. Specify only one of these. dof_bounds_rad (Optional[dict[str, tuple[float, float]]]): Lower and upper bounds for each degree of freedom (DOF), in radians. Each key is a DOF name (e.g., "RF_ThC_roll"), and each value is a tuple of (lower_bound, upper_bound). dof_bounds_deg (Optional[dict[str, tuple[float, float]]]): Same as `dof_bounds_rad`, but in degrees. Specify only one of these. """ self.segment_sizes = segment_sizes self.points_to_align = points_to_align self.skeleton = skeleton self.template = template if initial_angles_rad is not None and initial_angles_deg is not None: raise ValueError( "Specify only one of initial_angles_rad or initial_angles_deg" ) if initial_angles_rad is not None: self.initial_angles_rad = initial_angles_rad elif initial_angles_deg is not None: self.set_initial_angles_in_deg(initial_angles_deg) else: self.initial_angles_rad = {} # not set if dof_bounds_rad is not None and dof_bounds_deg is not None: raise ValueError("Specify only one of dof_bounds_rad or dof_bounds_deg") if dof_bounds_rad is not None: self.dof_bounds_rad = dof_bounds_rad elif dof_bounds_deg is not None: self.set_dof_bounds_in_deg(dof_bounds_deg) else: self.dof_bounds_rad = None # not set @property def dof_bounds_rad(self): return self._dof_bounds_rad @dof_bounds_rad.setter def dof_bounds_rad(self, value: Optional[dict[str, tuple[float, float]]]): for dof, (lower, upper) in value.items(): if not upper > lower: _logger.warning( f"Upper bound ({upper}) is not strictly greater than lower " f"bound ({lower}) for DoF {dof}" ) if not (-np.pi <= lower <= np.pi) or not (-np.pi <= upper <= np.pi): _logger.warning(f"DOF bounds for {dof} are out of range [-pi, pi]") self._dof_bounds_rad = value @property def initial_angles_rad(self): return self._initial_angles_rad @initial_angles_rad.setter def initial_angles_rad(self, value: Optional[dict[str, dict[str, np.ndarray]]]): for chain, initial_angles_by_stage in value.items(): for stage, initial_angles in initial_angles_by_stage.items(): if (initial_angles < -np.pi).any() or (initial_angles > np.pi).any(): _logger.warning( f"Some initial angles for chain {chain} at stage {stage} " f"are out of range [-pi, pi]" ) self._initial_angles_rad = value
[docs] def get_copy_of_initial_angles_in_deg(self) -> dict[str, dict[str, np.ndarray]]: """Returns a copy of the initial joint angles in degrees. Note that this is a *copy* of the underlying data, not a view of it. To mutate values, modify `.initial_angles_rad` in radians.""" initial_angles_deg = {} for leg_name, stages in self.initial_angles_rad.items(): initial_angles_deg[leg_name] = {} for stage, angles_rad in stages.items(): initial_angles_deg[leg_name][stage] = np.rad2deg(angles_rad) return initial_angles_deg
[docs] def get_copy_of_dof_bounds_in_deg(self) -> Optional[dict[str, tuple[float, float]]]: """Returns a copy of the DOF bounds in degrees. Note that this is a *copy* of the underlying data, not a view of it. To mutate values, modify `.dof_bounds_rad` in radians.""" if self.dof_bounds_rad is None: return None dof_bounds_deg = {} for dof, (lower_rad, upper_rad) in self.dof_bounds_rad.items(): dof_bounds_deg[dof] = (np.rad2deg(lower_rad), np.rad2deg(upper_rad)) return dof_bounds_deg
[docs] def set_initial_angles_in_deg( self, initial_angles_deg: dict[str, dict[str, np.ndarray]] ) -> None: """Sets the initial joint angles using values in degrees. This mutates the underlying data stored in `.initial_angles_rad`.""" initial_angles_rad = {} for leg_name, stages in initial_angles_deg.items(): initial_angles_rad[leg_name] = {} for stage, angles_deg in stages.items(): initial_angles_rad[leg_name][stage] = np.deg2rad(angles_deg) self.initial_angles_rad = initial_angles_rad
[docs] def set_dof_bounds_in_deg(self, dof_bounds_deg: dict[str, tuple[float, float]]): """Sets the DOF bounds using values in degrees. This mutates the underlying data stored in `.dof_bounds_rad`.""" dof_bounds_rad = {} for dof, (lower_deg, upper_deg) in dof_bounds_deg.items(): dof_bounds_rad[dof] = (np.deg2rad(lower_deg), np.deg2rad(upper_deg)) self.dof_bounds_rad = dof_bounds_rad
[docs] def deepcopy(self): return _deepcopy(self)
# Define default BodyConfig for NeuroMechFly _NMF_INITIAL_ANGLES_RAD = { "RF": { # Base ThC yaw pitch CTr pitch "stage_1": np.array([0.0, 0.45, -0.07, -2.14]), # Base ThC yaw pitch roll CTr pitch CTr roll "stage_2": np.array([0.0, 0.45, -0.07, -0.32, -2.14, 1.4]), # Base ThC yaw pitch roll CTr pitch CTr roll FTi pitch "stage_3": np.array([0.0, 0.45, -0.07, -0.32, -2.14, -1.25, 1.48, 0.0]), # Base ThC yaw pitch roll CTr pitch CTr roll FTi pitch TiTa pitch "stage_4": np.array([0.0, 0.45, -0.07, -0.32, -2.14, -1.25, 1.48, 0.0, 0.0]), }, # Same order for the contralateral leg "LF": { "stage_1": np.array([0.0, -0.45, -0.07, -2.14]), "stage_2": np.array([0.0, -0.45, -0.07, 0.32, -2.14, 1.4]), "stage_3": np.array([0.0, -0.45, -0.07, 0.32, -2.14, 1.25, 1.48, 0.0]), "stage_4": np.array([0.0, -0.45, -0.07, 0.32, -2.14, 1.25, 1.48, 0.0, 0.0]), }, } _NMF_BOUNDS_DEG = { "RF_ThC_roll": (-130, 50), "RF_ThC_yaw": (-50, 50), "RF_ThC_pitch": (-40, 60), "RF_CTr_pitch": (-180, 0), "RF_CTr_roll": (-150, 0), "RF_FTi_pitch": (0, 170), "RF_TiTa_pitch": (-150, 0), "LF_ThC_roll": (-50, 130), "LF_ThC_yaw": (-50, 50), "LF_ThC_pitch": (-40, 60), "LF_CTr_pitch": (-180, 0), "LF_CTr_roll": (0, 150), "LF_FTi_pitch": (0, 170), "LF_TiTa_pitch": (-150, 0), } _NMF_SIZE = { "RF_Coxa": 0.40, "RM_Coxa": 0.182, "RH_Coxa": 0.199, "LF_Coxa": 0.40, "LM_Coxa": 0.182, "LH_Coxa": 0.199, "RF_Femur": 0.69, "RM_Femur": 0.7829999999999999, "RH_Femur": 0.8360000000000001, "LF_Femur": 0.69, "LM_Femur": 0.7829999999999999, "LH_Femur": 0.8360000000000001, "RF_Tibia": 0.54, "RM_Tibia": 0.668, "RH_Tibia": 0.6849999999999998, "LF_Tibia": 0.54, "LM_Tibia": 0.668, "LH_Tibia": 0.6849999999999998, "RF_Tarsus": 0.63, "RM_Tarsus": 0.6949999999999998, "RH_Tarsus": 0.7950000000000002, "LF_Tarsus": 0.63, "LM_Tarsus": 0.6949999999999998, "LH_Tarsus": 0.7950000000000002, "RF": 2.26, "RM": 2.328, "RH": 2.515, "LF": 2.26, "LM": 2.328, "LH": 2.515, "Antenna": 0.2745906043549196, "Antenna_mid_thorax": 0.9355746896961248, } _NMF_PTS2ALIGN = { "R_head": [ "base_anten_R", "tip_anten_R", ], "RF_leg": [ "thorax_coxa_R", "coxa_femur_R", "femur_tibia_R", "tibia_tarsus_R", "claw_R", ], "Thorax": [ "thorax_wing_R", "thorax_midpoint_tether", "thorax_wing_L", ], "L_head": [ "base_anten_L", "tip_anten_L", ], "LF_leg": [ "thorax_coxa_L", "coxa_femur_L", "femur_tibia_L", "tibia_tarsus_L", "claw_L", ], } _NMF_SKELETON = [ "base_anten_R", "tip_anten_R", "thorax_coxa_R", "coxa_femur_R", "femur_tibia_R", "tibia_tarsus_R", "claw_R", "thorax_wing_R", "thorax_midpoint_tether", "thorax_wing_L", "base_anten_L", "tip_anten_L", "thorax_coxa_L", "coxa_femur_L", "femur_tibia_L", "tibia_tarsus_L", "claw_L", ] _NMF_TEMPLATE = { "RF_Coxa": np.array([0.33, -0.17, 1.07]), "RF_Femur": np.array([0.33, -0.17, 0.67]), "RF_Tibia": np.array([0.33, -0.17, -0.02]), "RF_Tarsus": np.array([0.33, -0.17, -0.56]), "RF_Claw": np.array([0.33, -0.17, -1.19]), "LF_Coxa": np.array([0.33, 0.17, 1.07]), "LF_Femur": np.array([0.33, 0.17, 0.67]), "LF_Tibia": np.array([0.33, 0.17, -0.02]), "LF_Tarsus": np.array([0.33, 0.17, -0.56]), "LF_Claw": np.array([0.33, 0.17, -1.19]), "R_Antenna_base": np.array([1.01, -0.10, 1.41]), "L_Antenna_base": np.array([1.01, 0.10, 1.41]), "R_Antenna_edge": np.array([1.06, -0.10, 1.14]), "L_Antenna_edge": np.array([1.06, 0.10, 1.14]), # "Labellum": np.array([0.75, 0.0, 0.81]), "R_post_vertical": np.array([0.7, -0.2, 1.59]), "L_post_vertical": np.array([0.7, 0.2, 1.59]), # "R_ant_orb": np.array([0.88, -0.18, 1.49]), # "L_ant_orb": np.array([0.88, 0.18, 1.49]), "R_wing": np.array([0.08, -0.4, 1.43]), "L_wing": np.array([0.08, 0.4, 1.43]), "Neck": np.array([0.53, 0.0, 1.3]), "Thorax_mid": np.array([0.08, 0.0, 1.43]), "L_dorsal_hum": np.array([0.41, 0.37, 1.32]), # "L_ant_notopleural": np.array([0.28, 0.39, 1.39]), "R_dorsal_hum": np.array([0.41, -0.37, 1.32]), # "R_ant_notopleural": np.array([0.30, -0.39, 1.39]), } neuromechfly_body_config = BodyConfig( segment_sizes=_NMF_SIZE, points_to_align=_NMF_PTS2ALIGN, skeleton=_NMF_SKELETON, template=_NMF_TEMPLATE, initial_angles_rad=_NMF_INITIAL_ANGLES_RAD, dof_bounds_deg=_NMF_BOUNDS_DEG, )