1use core::ffi::c_char;
9use core::ptr;
10use std::ffi::CString;
11use std::path::Path;
12
13use crate::body_pose::{collect, DetectedBodyPose};
14pub use crate::body_pose::{DetectedBodyPose as DetectedHandPose, JointPoint};
15use crate::error::{from_swift, VisionError};
16use crate::ffi;
17use crate::recognized_points::{RecognizedPointsObservation, VisionRecognizedPoint};
18
19macro_rules! string_enum {
20 (
21 $(#[$meta:meta])*
22 pub enum $name:ident {
23 $( $variant:ident => $value:literal ),+ $(,)?
24 }
25 ) => {
26 $(#[$meta])*
27 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28 pub enum $name {
29 $( $variant ),+
30 }
31
32 impl $name {
33 pub const ALL: &'static [Self] = &[
34 $( Self::$variant ),+
35 ];
36
37 #[must_use]
38 pub const fn as_str(self) -> &'static str {
39 match self {
40 $( Self::$variant => $value ),+
41 }
42 }
43
44 #[allow(clippy::should_implement_trait)]
45 #[must_use]
46 pub fn from_str(value: &str) -> Option<Self> {
47 match value {
48 $( $value => Some(Self::$variant), )+
49 _ => None,
50 }
51 }
52 }
53 };
54}
55
56string_enum! {
57 pub enum HumanHandPoseJointName {
59 Wrist => "VNHLKWRI",
60 ThumbCmc => "VNHLKTCMC",
61 ThumbMp => "VNHLKTMP",
62 ThumbIp => "VNHLKTIP",
63 ThumbTip => "VNHLKTTIP",
64 IndexMcp => "VNHLKIMCP",
65 IndexPip => "VNHLKIPIP",
66 IndexDip => "VNHLKIDIP",
67 IndexTip => "VNHLKITIP",
68 MiddleMcp => "VNHLKMMCP",
69 MiddlePip => "VNHLKMPIP",
70 MiddleDip => "VNHLKMDIP",
71 MiddleTip => "VNHLKMTIP",
72 RingMcp => "VNHLKRMCP",
73 RingPip => "VNHLKRPIP",
74 RingDip => "VNHLKRDIP",
75 RingTip => "VNHLKRTIP",
76 LittleMcp => "VNHLKPMCP",
77 LittlePip => "VNHLKPPIP",
78 LittleDip => "VNHLKPDIP",
79 LittleTip => "VNHLKPTIP",
80 }
81}
82
83string_enum! {
84 pub enum HumanHandPoseJointGroupName {
86 Thumb => "VNHLRKT",
87 IndexFinger => "VNHLRKI",
88 MiddleFinger => "VNHLRKM",
89 RingFinger => "VNHLRKR",
90 LittleFinger => "VNHLRKP",
91 All => "VNIPOAll",
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub enum HandChirality {
98 Unknown,
99 Left,
100 Right,
101}
102
103#[derive(Debug, Clone, PartialEq)]
105pub struct HumanHandPoseObservation {
106 pub recognized_points: RecognizedPointsObservation,
107 pub available_joint_names: Vec<String>,
108 pub available_joint_group_names: Vec<String>,
109 pub chirality: HandChirality,
110}
111
112const SUPPORTED_JOINT_NAMES: &[&str] = &[
113 HumanHandPoseJointName::Wrist.as_str(),
114 HumanHandPoseJointName::ThumbCmc.as_str(),
115 HumanHandPoseJointName::ThumbMp.as_str(),
116 HumanHandPoseJointName::ThumbIp.as_str(),
117 HumanHandPoseJointName::ThumbTip.as_str(),
118 HumanHandPoseJointName::IndexMcp.as_str(),
119 HumanHandPoseJointName::IndexPip.as_str(),
120 HumanHandPoseJointName::IndexDip.as_str(),
121 HumanHandPoseJointName::IndexTip.as_str(),
122 HumanHandPoseJointName::MiddleMcp.as_str(),
123 HumanHandPoseJointName::MiddlePip.as_str(),
124 HumanHandPoseJointName::MiddleDip.as_str(),
125 HumanHandPoseJointName::MiddleTip.as_str(),
126 HumanHandPoseJointName::RingMcp.as_str(),
127 HumanHandPoseJointName::RingPip.as_str(),
128 HumanHandPoseJointName::RingDip.as_str(),
129 HumanHandPoseJointName::RingTip.as_str(),
130 HumanHandPoseJointName::LittleMcp.as_str(),
131 HumanHandPoseJointName::LittlePip.as_str(),
132 HumanHandPoseJointName::LittleDip.as_str(),
133 HumanHandPoseJointName::LittleTip.as_str(),
134];
135
136const SUPPORTED_JOINT_GROUP_NAMES: &[&str] = &[
137 HumanHandPoseJointGroupName::Thumb.as_str(),
138 HumanHandPoseJointGroupName::IndexFinger.as_str(),
139 HumanHandPoseJointGroupName::MiddleFinger.as_str(),
140 HumanHandPoseJointGroupName::RingFinger.as_str(),
141 HumanHandPoseJointGroupName::LittleFinger.as_str(),
142 HumanHandPoseJointGroupName::All.as_str(),
143];
144
145impl HumanHandPoseObservation {
146 #[must_use]
147 pub const fn supported_joint_name_keys() -> &'static [HumanHandPoseJointName] {
148 HumanHandPoseJointName::ALL
149 }
150
151 #[must_use]
152 pub const fn supported_joint_names() -> &'static [&'static str] {
153 SUPPORTED_JOINT_NAMES
154 }
155
156 #[must_use]
157 pub const fn supported_joint_group_name_keys() -> &'static [HumanHandPoseJointGroupName] {
158 HumanHandPoseJointGroupName::ALL
159 }
160
161 #[must_use]
162 pub const fn supported_joint_group_names() -> &'static [&'static str] {
163 SUPPORTED_JOINT_GROUP_NAMES
164 }
165
166 #[must_use]
167 pub fn recognized_point(
168 &self,
169 joint_name: HumanHandPoseJointName,
170 ) -> Option<VisionRecognizedPoint> {
171 self.recognized_points.recognized_point(joint_name.as_str())
172 }
173
174 #[must_use]
175 pub fn into_detected_hand_pose(self) -> DetectedHandPose {
176 self.into()
177 }
178}
179
180impl From<DetectedHandPose> for HumanHandPoseObservation {
181 fn from(value: DetectedHandPose) -> Self {
182 let body = crate::body_pose::HumanBodyPoseObservation::from(value);
183 Self {
184 recognized_points: body.recognized_points,
185 available_joint_names: body.available_joint_names,
186 available_joint_group_names: Self::supported_joint_group_names()
187 .iter()
188 .map(|name| (*name).to_string())
189 .collect(),
190 chirality: HandChirality::Unknown,
191 }
192 }
193}
194
195impl From<HumanHandPoseObservation> for DetectedHandPose {
196 fn from(value: HumanHandPoseObservation) -> Self {
197 crate::body_pose::HumanBodyPoseObservation {
198 recognized_points: value.recognized_points,
199 available_joint_names: value.available_joint_names,
200 available_joint_group_names: value.available_joint_group_names,
201 }
202 .into()
203 }
204}
205
206pub fn detect_human_hand_pose_in_path(
213 path: impl AsRef<Path>,
214 max_hands: usize,
215) -> Result<Vec<DetectedBodyPose>, VisionError> {
216 let path_str = path
217 .as_ref()
218 .to_str()
219 .ok_or_else(|| VisionError::InvalidArgument("non-UTF-8 path".into()))?;
220 let path_c = CString::new(path_str)
221 .map_err(|e| VisionError::InvalidArgument(format!("path NUL byte: {e}")))?;
222
223 let mut out_array: *mut core::ffi::c_void = ptr::null_mut();
224 let mut out_count: usize = 0;
225 let mut err_msg: *mut c_char = ptr::null_mut();
226 let status = unsafe {
228 ffi::vn_detect_human_hand_pose_in_path(
229 path_c.as_ptr(),
230 max_hands,
231 &mut out_array,
232 &mut out_count,
233 &mut err_msg,
234 )
235 };
236 if status != ffi::status::OK {
237 return Err(unsafe { from_swift(status, err_msg) });
239 }
240 Ok(unsafe { collect(out_array, out_count) })
242}
243
244pub fn detect_human_hand_pose_observations_in_path(
250 path: impl AsRef<Path>,
251 max_hands: usize,
252) -> Result<Vec<HumanHandPoseObservation>, VisionError> {
253 detect_human_hand_pose_in_path(path, max_hands)
254 .map(|poses| poses.into_iter().map(Into::into).collect())
255}