Skip to content

Commit 0863c73

Browse files
committed
Add skeleton resolver and style field updates
Introduce typed skeleton resolution primitives (`SkeletonEdge`, `SkeletonDefinition`, `ResolvedSkeleton`) and a `resolve_skeleton` helper that validates keypoint inputs, detects duplicates/missing names, and raises `SkeletonResolutionError` with clear messages. Also align rendering with renamed style fields (`mode` -> `color_mode`, `color` -> `color_bgr`) so initialization and draw paths use the updated `SkeletonStyle` API, and fix the module path header comment.
1 parent 873db89 commit 0863c73

1 file changed

Lines changed: 70 additions & 6 deletions

File tree

‎dlclivegui/display/skeleton.py‎

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Skeleton definition, validation, and drawing utilities."""
22

3-
# dlclivegui/utils/skeleton.py
3+
# dlclivegui/display/skeleton.py
44
from __future__ import annotations
55

66
import 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+
51115
class 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

Comments
 (0)