Skip to content

Commit b86a0d6

Browse files
authored
Simplify hand pose definitions. Cache analyzed hand pose data for use across multiple poses in a pose set. (#3)
1 parent 139f3e1 commit b86a0d6

24 files changed

+603
-899
lines changed

README.md

+21-17
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,11 @@ New hand poses can be made by creating new Hand Pose Resource instances.
5454
Hand Pose Resources consist of:
5555
* A Pose Name (reported in the pose detector signals)
5656
* A Threshold (a minimal fitness threshold to report the pose)
57-
* An array of Hand Pose Rules
57+
* A set of fitness functions to apply to each pose component
5858

59-
### Hand Pose Rules
59+
### Pose Components
6060

61-
![Hand Pose Rule](/docs/hand_pose_rule.png)
62-
63-
Hand Pose Rules consist of:
64-
* A Rule Name (useful for tuning and debugging)
65-
* A Rule Type [Flexion, Curl, Abduciton, and Tip-Distance]
66-
* A Finger
67-
* A second Finger (for Abduction and Tip-Distance)
68-
* Fitness Function Terms (min, lower, upper, max)
69-
70-
### Rule Types
71-
72-
| Rule | Description |
61+
| Type | Description |
7362
| :--- | :---------- |
7463
| Flexion | The angle (in degrees) of a fingers proximal joint curving into the palm to make a fist. |
7564
| Curl | The curl (in degrees) of a finger from the proximal to the distal joints. |
@@ -78,10 +67,25 @@ Hand Pose Rules consist of:
7867

7968
### Fitness Function
8069

81-
Each rule produces a measurement in either degrees or millimeters. This measurement is translated to a fitness value in the range 0..1 using a transform defined by the fitness function terms. Values outside of the min/max range have a fitness of 0. Values inside the lower/upper range have a fitness of 1, and min-lower and upper-max are connected by a smoothstep function:
82-
![Fitness Transform](/docs/fitness-transform.png)
70+
The fitness function converts a measurement (degrees or milimeters) into a fitness in the range 0..1 with 0 being a bad match, and 1 being a perfect match. Two types of fitness function are supported:
71+
* Smoothstep
72+
* Range
73+
74+
The fitness of a Hand Pose is the product of the fitness of all the components.
75+
76+
#### Smooth-Step Function
77+
78+
The Smooth-Step function transitions from 0 to 1 over the specified range. The paramerters may be reversed to reverse the function.
79+
80+
![SmoothStep Positive](/docs/smootstep_positive.png)
81+
![SmoothStep Negative](/docs/smootstep_negative.png)
82+
83+
#### Range Function
84+
85+
The Range function provides non-zero values in a finite range.
86+
87+
![Fitness Transform](/docs/range_function.png)
8388

84-
The fitness of a Hand Pose is the product of the fitness of all the rules.
8589

8690
## Designing and Tuning
8791

VERSIONS.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 1.1.0
22
- Moved fitness function to separate resource
3+
- Simplified hand resource definition
34

45
# 1.0.0
56
- Initial Revision

addons/hand_pose_detector/hand_info.gd

-182
This file was deleted.

addons/hand_pose_detector/hand_pose.gd

+91-25
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,104 @@ extends Resource
1515
## Detection threshold
1616
@export_range(0.0, 1.0) var threshold : float = 0.5
1717

18-
## Array of hand pose rules
19-
@export var rules : Array[HandPoseRule] = []
18+
# Flexion group
19+
@export_group("Flexion", "flexion_")
2020

21-
## Rules validated
22-
var _validated := false
21+
## Flexion Thumb Function
22+
@export var flexion_thumb : FitnessFunction
23+
24+
## Flexion Index Function
25+
@export var flexion_index : FitnessFunction
26+
27+
## Flexion Middle Function
28+
@export var flexion_middle : FitnessFunction
29+
30+
## Flexion Ring Function
31+
@export var flexion_ring : FitnessFunction
32+
33+
## Flexion Pinky Function
34+
@export var flexion_pinky : FitnessFunction
35+
36+
# Curl group
37+
@export_group("Curl", "curl_")
38+
39+
## Curl Thumb Function
40+
@export var curl_thumb : FitnessFunction
41+
42+
## Curl Index Function
43+
@export var curl_index : FitnessFunction
44+
45+
## Curl Middle Function
46+
@export var curl_middle : FitnessFunction
47+
48+
## Curl Ring Function
49+
@export var curl_ring : FitnessFunction
50+
51+
## Curl Pinky Function
52+
@export var curl_pinky : FitnessFunction
53+
54+
# Abduction group
55+
@export_group("Abduction", "abduction_")
56+
57+
## Abduction Thumb-Index Function
58+
@export var abduction_thumb_index : FitnessFunction
59+
60+
## Abduction Index-Middle Function
61+
@export var abduction_index_middle : FitnessFunction
62+
63+
## Abduction Middle-Ring Function
64+
@export var abduction_middle_ring : FitnessFunction
65+
66+
## Abduction Ring-Pinky Function
67+
@export var abduction_ring_pinky : FitnessFunction
68+
69+
# Tip-distance group
70+
@export_group("Tip-Distance", "distance_")
71+
72+
## Tip-Distance Thumb-Index Function
73+
@export var distance_thumb_index : FitnessFunction
74+
75+
## Tip-Distance Thumb-Middle Function
76+
@export var distance_thumb_middle : FitnessFunction
77+
78+
## Tip-Distance Thumb-Ring Function
79+
@export var distance_thumb_ring : FitnessFunction
80+
81+
## Tip-Distance Thumb-Pinky Function
82+
@export var distance_thumb_pinky : FitnessFunction
2383

2484

2585
## Returns a fitness value in the range 0..1 as to how well the [param hand]
2686
## matches the hand pose rules.
27-
func get_fitness(hand : XRHandTracker) -> float:
28-
# Fail if no rules
29-
if rules.size() == 0:
30-
return 0
31-
32-
# Perform rule validation on first use
33-
if not _validated:
34-
_validated = true
35-
if pose_name == "":
36-
push_warning("Hand Pose: %s name not specified" % resource_path);
37-
if threshold <= 0.0:
38-
push_warning("Hand Pose: %s invalid threshold" % pose_name)
39-
for r in rules:
40-
var warnings = r.get_warnings()
41-
if warnings.size() > 0:
42-
push_warning("Hand Pose: %s rule issues" % pose_name)
43-
for w in warnings.size():
44-
push_warning(" Rule %d: %s" % [w, warnings[w]])
45-
87+
func get_fitness(hand : HandPoseData) -> float:
4688
# Process the fitness of all rules
4789
var fitness := 1.0
48-
for r in rules:
49-
fitness *= r.get_fitness(hand)
90+
91+
# Apply flexion rules
92+
if flexion_thumb: fitness *= flexion_thumb.calculate(hand.flx_thumb)
93+
if flexion_index: fitness *= flexion_index.calculate(hand.flx_index)
94+
if flexion_middle: fitness *= flexion_middle.calculate(hand.flx_middle)
95+
if flexion_ring: fitness *= flexion_ring.calculate(hand.flx_ring)
96+
if flexion_pinky: fitness *= flexion_pinky.calculate(hand.flx_pinky)
97+
98+
# Apply curl rules
99+
if curl_thumb: fitness *= curl_thumb.calculate(hand.crl_thumb)
100+
if curl_index: fitness *= curl_index.calculate(hand.crl_index)
101+
if curl_middle: fitness *= curl_middle.calculate(hand.crl_middle)
102+
if curl_ring: fitness *= curl_ring.calculate(hand.crl_ring)
103+
if curl_pinky: fitness *= curl_pinky.calculate(hand.crl_pinky)
104+
105+
# Apply abduction rules
106+
if abduction_thumb_index: fitness *= abduction_thumb_index.calculate(hand.abd_thumb)
107+
if abduction_index_middle: fitness *= abduction_index_middle.calculate(hand.abd_index)
108+
if abduction_middle_ring: fitness *= abduction_middle_ring.calculate(hand.abd_middle)
109+
if abduction_ring_pinky: fitness *= abduction_ring_pinky.calculate(hand.abd_ring)
110+
111+
# Apply tip-distance rules
112+
if distance_thumb_index: fitness *= distance_thumb_index.calculate(hand.dst_index)
113+
if distance_thumb_middle: fitness *= distance_thumb_middle.calculate(hand.dst_middle)
114+
if distance_thumb_ring: fitness *= distance_thumb_ring.calculate(hand.dst_ring)
115+
if distance_thumb_pinky: fitness *= distance_thumb_pinky.calculate(hand.dst_pinky)
50116

51117
# If the fitness is below the threshold then fail
52118
if fitness < threshold:

0 commit comments

Comments
 (0)