11"""Skeleton definition, validation, and drawing utilities."""
22
3- # dlclivegui/utils /skeleton.py
3+ # dlclivegui/display /skeleton.py
44from __future__ import annotations
55
66import json
@@ -43,11 +43,75 @@ def should_disable(self) -> bool:
4343 }
4444
4545
46+ @dataclass (frozen = True , slots = True )
47+ class SkeletonEdge :
48+ start : str
49+ end : str
50+
51+
52+ @dataclass (frozen = True , slots = True )
53+ class SkeletonDefinition :
54+ identifier : str
55+ display_name : str
56+ edges : tuple [SkeletonEdge , ...]
57+
58+
59+ @dataclass (frozen = True , slots = True )
60+ class ResolvedSkeleton :
61+ definition : SkeletonDefinition
62+ keypoint_names : tuple [str , ...]
63+ edges : tuple [tuple [int , int ], ...]
64+
65+
66+ def resolve_skeleton (
67+ definition : SkeletonDefinition ,
68+ keypoint_names : list [str ] | tuple [str , ...],
69+ ) -> ResolvedSkeleton :
70+ names = tuple (keypoint_names )
71+
72+ if not names :
73+ raise SkeletonResolutionError ("Cannot resolve a skeleton without keypoint names." )
74+
75+ if len (set (names )) != len (names ):
76+ raise SkeletonResolutionError ("Cannot resolve a skeleton against duplicate keypoint names." )
77+
78+ name_to_index = {name : index for index , name in enumerate (names )}
79+
80+ resolved_edges : list [tuple [int , int ]] = []
81+ missing_names : set [str ] = set ()
82+
83+ for edge in definition .edges :
84+ start_index = name_to_index .get (edge .start )
85+ end_index = name_to_index .get (edge .end )
86+
87+ if start_index is None :
88+ missing_names .add (edge .start )
89+ if end_index is None :
90+ missing_names .add (edge .end )
91+
92+ if start_index is not None and end_index is not None :
93+ resolved_edges .append ((start_index , end_index ))
94+
95+ if missing_names :
96+ missing = ", " .join (sorted (missing_names ))
97+ raise SkeletonResolutionError (f"Skeleton keypoints are absent from the pose output: { missing } ." )
98+
99+ return ResolvedSkeleton (
100+ definition = definition ,
101+ keypoint_names = names ,
102+ edges = tuple (resolved_edges ),
103+ )
104+
105+
46106# ############ #
47107# Exceptions #
48108# ############ #
49109
50110
111+ class SkeletonResolutionError (ValueError ):
112+ """Raised when a skeleton cannot be aligned with pose keypoints."""
113+
114+
51115class SkeletonError (ValueError ):
52116 """Raised when a skeleton definition is invalid."""
53117
@@ -217,8 +281,8 @@ def __init__(self, model: SkeletonModel):
217281 self .edges = model .edges
218282
219283 self .style = SkeletonStyle (
220- mode = model .style .mode ,
221- color = model .style .color ,
284+ color_mode = model .style .mode ,
285+ color_bgr = model .style .color ,
222286 thickness = model .style .thickness ,
223287 gradient_steps = model .style .gradient_steps ,
224288 scale_with_zoom = model .style .scale_with_zoom ,
@@ -295,7 +359,7 @@ def draw(
295359 th = st .effective_thickness (sx , sy )
296360
297361 # if gradient mode, require keypoint_colors aligned with keypoint order
298- if st .mode == SkeletonColorMode .GRADIENT_KEYPOINTS :
362+ if st .color_mode == SkeletonColorMode .GRADIENT_KEYPOINTS :
299363 if keypoint_colors is None or len (keypoint_colors ) != len (self .keypoints ):
300364 return SkeletonRenderStatus (
301365 SkeletonRenderCode .KEYPOINT_COUNT_MISMATCH ,
@@ -313,13 +377,13 @@ def draw(
313377 p1 = (int (xi * sx + ox ), int (yi * sy + oy ))
314378 p2 = (int (xj * sx + ox ), int (yj * sy + oy ))
315379
316- if st .mode == SkeletonColorMode .GRADIENT_KEYPOINTS :
380+ if st .color_mode == SkeletonColorMode .GRADIENT_KEYPOINTS :
317381 c1 = keypoint_colors [i ]
318382 c2 = keypoint_colors [j ]
319383 self ._draw_gradient_edge (overlay , p1 , p2 , c1 , c2 , th , st .gradient_steps )
320384 else :
321385 # SOLID: priority edge_colors > override > style.color > default_color
322- color = self .edge_colors .get ((i , j ), color_override or st .color or self .default_color )
386+ color = self .edge_colors .get ((i , j ), color_override or st .color_bgr or self .default_color )
323387 cv2 .line (overlay , p1 , p2 , color , th , lineType = cv2 .LINE_AA )
324388
325389 return SkeletonRenderStatus (SkeletonRenderCode .OK , "" )
0 commit comments