Source code for lisbet.drawing

"""Drawing specifications for LISBET."""

from dataclasses import dataclass, field

import matplotlib.colors as mcolors


[docs] @dataclass class BodySpecs: """ Specification for drawing a body (skeleton, polygons, keypoints) for a given species or individual. Parameters ---------- skeleton_edges : list of tuple of str List of (keypoint1, keypoint2) pairs to draw as skeleton lines. polygons : list of list of str, optional List of lists of keypoint names to fill as polygons. keypoint_colors : dict of str to str, optional Mapping from keypoint name to color. skeleton_color : str, optional Color for skeleton lines. polygon_color : str, optional Color for filled polygons. polygon_alpha : float, optional Alpha (opacity) for polygons. keypoint_marker : str, optional Marker style for keypoints. keypoint_size : int, optional Size of keypoint markers. """ skeleton_edges: list[tuple[str, str]] polygons: list[list[str]] = field(default_factory=list) keypoint_colors: dict[str, str] = field(default_factory=dict) skeleton_color: str = "lime" skeleton_thickness: int = 1 polygon_color: str = "cyan" polygon_alpha: float = 0.3 keypoint_marker: str = "o" keypoint_size: int = 6
[docs] def get_keypoint_color(self, keypoint: str) -> str: """Return the color for a given keypoint, or white if not specified.""" return self.keypoint_colors.get(keypoint, "white")
[docs] def color_to_bgr(color): """ Convert a matplotlib color name or hex string to a BGR tuple for OpenCV. Accepts color names (e.g. 'red'), hex strings (e.g. '#ff0000'), or BGR tuples. """ if ( isinstance(color, tuple) and len(color) == 3 and all(isinstance(x, int) for x in color) ): return color # Already BGR if ( isinstance(color, tuple) and len(color) == 3 and all(isinstance(x, float) for x in color) ): # Assume RGB float 0-1 rgb = tuple(int(255 * x) for x in color) return (rgb[2], rgb[1], rgb[0]) if isinstance(color, str): if color.startswith("#"): rgb = mcolors.to_rgb(color) else: rgb = mcolors.to_rgb(mcolors.CSS4_COLORS.get(color, color)) rgb = tuple(int(255 * x) for x in rgb) return (rgb[2], rgb[1], rgb[0]) # fallback return (255, 255, 255)
# Registry of common species/body layouts for LISBET body_specs_registry: dict[str, BodySpecs] = { "mouse": BodySpecs( skeleton_edges=[ ("nose", "neck"), ("neck", "tail"), ("nose", "left_ear"), ("left_ear", "neck"), ("nose", "right_ear"), ("right_ear", "neck"), ("neck", "left_hip"), ("left_hip", "tail"), ("neck", "right_hip"), ("right_hip", "tail"), ], polygons=[ ["nose", "left_ear", "neck", "right_ear"], ["neck", "left_hip", "tail", "right_hip"], ], keypoint_colors={ "nose": "red", "left_ear": "orange", "right_ear": "orange", "neck": "yellow", "tail": "blue", "left_hip": "green", "right_hip": "green", }, skeleton_color="green", polygon_color="dodgerblue", polygon_alpha=0.7, keypoint_marker="o", keypoint_size=3, ), "fly": BodySpecs( skeleton_edges=[ ("head", "thorax"), ("thorax", "abdomen"), ], polygons=[], keypoint_colors={ "head": "orange", "thorax": "yellow", "abdomen": "blue", }, skeleton_color="purple", polygon_color="cyan", polygon_alpha=0.3, keypoint_marker="^", keypoint_size=6, ), }