Skip to main content

oxihuman_morph/
muscle_action_unit.rs

1//! FACS-like Action Units for facial expression control.
2
3#[allow(dead_code)]
4pub struct ActionUnit {
5    pub au_code: u32,
6    pub name: String,
7    pub intensity: f32,
8    pub morph_targets: Vec<(String, f32)>,
9    pub bilateral: bool,
10}
11
12#[allow(dead_code)]
13pub struct AuSet {
14    pub units: Vec<ActionUnit>,
15    pub name: String,
16}
17
18#[allow(dead_code)]
19pub struct AuFrame {
20    pub time: f32,
21    pub au_intensities: Vec<(u32, f32)>,
22}
23
24#[allow(dead_code)]
25pub fn standard_facs_set() -> AuSet {
26    let mut set = new_au_set("FACS Standard");
27    let au_defs: &[(u32, &str, bool)] = &[
28        (1, "Inner Brow Raise", true),
29        (2, "Outer Brow Raise", true),
30        (4, "Brow Lowerer", true),
31        (5, "Upper Lid Raiser", true),
32        (6, "Cheek Raiser", true),
33        (7, "Lid Tightener", true),
34        (9, "Nose Wrinkler", true),
35        (10, "Upper Lip Raiser", false),
36        (12, "Lip Corner Puller", true),
37        (15, "Lip Corner Depressor", true),
38        (17, "Chin Raiser", false),
39        (20, "Lip Stretcher", true),
40        (23, "Lip Tightener", false),
41        (25, "Lips Part", false),
42        (26, "Jaw Drop", false),
43    ];
44    for &(code, name, bilateral) in au_defs {
45        let targets = vec![(format!("au_{code}"), 1.0_f32)];
46        add_action_unit(
47            &mut set,
48            ActionUnit {
49                au_code: code,
50                name: name.to_string(),
51                intensity: 0.0,
52                morph_targets: targets,
53                bilateral,
54            },
55        );
56    }
57    set
58}
59
60#[allow(dead_code)]
61pub fn new_au_set(name: &str) -> AuSet {
62    AuSet {
63        units: Vec::new(),
64        name: name.to_string(),
65    }
66}
67
68#[allow(dead_code)]
69pub fn add_action_unit(set: &mut AuSet, au: ActionUnit) {
70    set.units.push(au);
71}
72
73#[allow(dead_code)]
74pub fn get_au(set: &AuSet, code: u32) -> Option<&ActionUnit> {
75    set.units.iter().find(|u| u.au_code == code)
76}
77
78#[allow(dead_code)]
79pub fn set_au_intensity(set: &mut AuSet, code: u32, intensity: f32) -> bool {
80    let clamped = intensity.clamp(0.0, 1.0);
81    if let Some(au) = set.units.iter_mut().find(|u| u.au_code == code) {
82        au.intensity = clamped;
83        true
84    } else {
85        false
86    }
87}
88
89#[allow(dead_code)]
90pub fn evaluate_au_set(set: &AuSet) -> Vec<(String, f32)> {
91    let mut contributions: std::collections::HashMap<String, f32> =
92        std::collections::HashMap::new();
93    for au in &set.units {
94        if au.intensity <= 0.0 {
95            continue;
96        }
97        for (target_name, weight_mult) in &au.morph_targets {
98            let entry = contributions.entry(target_name.clone()).or_insert(0.0);
99            *entry += au.intensity * weight_mult;
100        }
101    }
102    let mut result: Vec<(String, f32)> = contributions.into_iter().collect();
103    result.sort_by(|a, b| a.0.cmp(&b.0));
104    for (_, v) in &mut result {
105        *v = v.clamp(0.0, 1.0);
106    }
107    result
108}
109
110#[allow(dead_code)]
111pub fn au_to_emotion(set: &AuSet) -> &'static str {
112    let active: Vec<u32> = set
113        .units
114        .iter()
115        .filter(|u| u.intensity > 0.5)
116        .map(|u| u.au_code)
117        .collect();
118
119    let has = |code: u32| active.contains(&code);
120
121    if has(1) && has(4) && has(15) {
122        "sadness"
123    } else if has(2) && has(5) && has(20) && has(26) {
124        "fear"
125    } else if has(4) && has(5) && has(23) && has(25) {
126        "anger"
127    } else if has(1) && has(2) && has(5) && has(26) {
128        "surprise"
129    } else if has(9) && has(15) && has(16) {
130        "disgust"
131    } else if has(6) && has(12) {
132        "happiness"
133    } else {
134        "neutral"
135    }
136}
137
138#[allow(dead_code)]
139pub fn au_count(set: &AuSet) -> usize {
140    set.units.len()
141}
142
143#[allow(dead_code)]
144pub fn active_aus(set: &AuSet) -> Vec<&ActionUnit> {
145    set.units.iter().filter(|u| u.intensity > 0.01).collect()
146}
147
148#[allow(dead_code)]
149pub fn reset_all_aus(set: &mut AuSet) {
150    for au in &mut set.units {
151        au.intensity = 0.0;
152    }
153}
154
155#[allow(dead_code)]
156pub fn blend_au_frames(a: &AuFrame, b: &AuFrame, t: f32) -> AuFrame {
157    let t = t.clamp(0.0, 1.0);
158    let mut map: std::collections::HashMap<u32, f32> = std::collections::HashMap::new();
159    for &(code, intensity) in &a.au_intensities {
160        map.insert(code, intensity * (1.0 - t));
161    }
162    for &(code, intensity) in &b.au_intensities {
163        let entry = map.entry(code).or_insert(0.0);
164        *entry += intensity * t;
165    }
166    let mut intensities: Vec<(u32, f32)> = map.into_iter().collect();
167    intensities.sort_by_key(|&(code, _)| code);
168    AuFrame {
169        time: a.time * (1.0 - t) + b.time * t,
170        au_intensities: intensities,
171    }
172}
173
174#[allow(dead_code)]
175pub fn au_frame_from_set(set: &AuSet, time: f32) -> AuFrame {
176    let au_intensities = set.units.iter().map(|u| (u.au_code, u.intensity)).collect();
177    AuFrame {
178        time,
179        au_intensities,
180    }
181}
182
183#[allow(dead_code)]
184pub fn apply_au_frame(set: &mut AuSet, frame: &AuFrame) {
185    for &(code, intensity) in &frame.au_intensities {
186        set_au_intensity(set, code, intensity);
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_standard_facs_set_has_15_aus() {
196        let set = standard_facs_set();
197        assert_eq!(au_count(&set), 15);
198    }
199
200    #[test]
201    fn test_new_au_set() {
202        let set = new_au_set("Test Set");
203        assert_eq!(set.name, "Test Set");
204        assert_eq!(au_count(&set), 0);
205    }
206
207    #[test]
208    fn test_add_action_unit() {
209        let mut set = new_au_set("Test");
210        add_action_unit(
211            &mut set,
212            ActionUnit {
213                au_code: 1,
214                name: "Inner Brow Raise".to_string(),
215                intensity: 0.0,
216                morph_targets: vec![("brow_raise".to_string(), 1.0)],
217                bilateral: true,
218            },
219        );
220        assert_eq!(au_count(&set), 1);
221    }
222
223    #[test]
224    fn test_get_au() {
225        let set = standard_facs_set();
226        let au = get_au(&set, 12);
227        assert!(au.is_some());
228        assert_eq!(au.expect("should succeed").au_code, 12);
229    }
230
231    #[test]
232    fn test_get_au_not_found() {
233        let set = standard_facs_set();
234        assert!(get_au(&set, 99).is_none());
235    }
236
237    #[test]
238    fn test_set_au_intensity() {
239        let mut set = standard_facs_set();
240        let result = set_au_intensity(&mut set, 12, 0.8);
241        assert!(result);
242        let au = get_au(&set, 12).expect("should succeed");
243        assert!((au.intensity - 0.8).abs() < 1e-6);
244    }
245
246    #[test]
247    fn test_set_au_intensity_clamps() {
248        let mut set = standard_facs_set();
249        set_au_intensity(&mut set, 1, 2.0);
250        assert!((get_au(&set, 1).expect("should succeed").intensity - 1.0).abs() < 1e-6);
251    }
252
253    #[test]
254    fn test_set_au_intensity_not_found() {
255        let mut set = standard_facs_set();
256        assert!(!set_au_intensity(&mut set, 99, 0.5));
257    }
258
259    #[test]
260    fn test_evaluate_au_set() {
261        let mut set = standard_facs_set();
262        set_au_intensity(&mut set, 12, 0.5);
263        let result = evaluate_au_set(&set);
264        assert!(!result.is_empty());
265        let au12 = result.iter().find(|(name, _)| name == "au_12");
266        assert!(au12.is_some());
267        assert!((au12.expect("should succeed").1 - 0.5).abs() < 1e-6);
268    }
269
270    #[test]
271    fn test_au_to_emotion_happiness() {
272        let mut set = standard_facs_set();
273        set_au_intensity(&mut set, 6, 1.0);
274        set_au_intensity(&mut set, 12, 1.0);
275        assert_eq!(au_to_emotion(&set), "happiness");
276    }
277
278    #[test]
279    fn test_au_to_emotion_neutral() {
280        let set = standard_facs_set();
281        assert_eq!(au_to_emotion(&set), "neutral");
282    }
283
284    #[test]
285    fn test_active_aus() {
286        let mut set = standard_facs_set();
287        set_au_intensity(&mut set, 1, 0.5);
288        set_au_intensity(&mut set, 12, 0.0);
289        let active = active_aus(&set);
290        assert_eq!(active.len(), 1);
291        assert_eq!(active[0].au_code, 1);
292    }
293
294    #[test]
295    fn test_reset_all_aus() {
296        let mut set = standard_facs_set();
297        set_au_intensity(&mut set, 1, 0.8);
298        set_au_intensity(&mut set, 12, 0.6);
299        reset_all_aus(&mut set);
300        assert!(active_aus(&set).is_empty());
301    }
302
303    #[test]
304    fn test_au_frame_from_set() {
305        let mut set = standard_facs_set();
306        set_au_intensity(&mut set, 12, 0.7);
307        let frame = au_frame_from_set(&set, 1.5);
308        assert!((frame.time - 1.5).abs() < 1e-6);
309        let au12_entry = frame.au_intensities.iter().find(|&&(code, _)| code == 12);
310        assert!(au12_entry.is_some());
311        assert!((au12_entry.expect("should succeed").1 - 0.7).abs() < 1e-6);
312    }
313
314    #[test]
315    fn test_apply_au_frame() {
316        let mut set = standard_facs_set();
317        set_au_intensity(&mut set, 12, 0.7);
318        let frame = au_frame_from_set(&set, 0.0);
319        let mut set2 = standard_facs_set();
320        apply_au_frame(&mut set2, &frame);
321        assert!((get_au(&set2, 12).expect("should succeed").intensity - 0.7).abs() < 1e-6);
322    }
323
324    #[test]
325    fn test_blend_au_frames() {
326        let mut set_a = standard_facs_set();
327        set_au_intensity(&mut set_a, 12, 0.0);
328        let frame_a = au_frame_from_set(&set_a, 0.0);
329
330        let mut set_b = standard_facs_set();
331        set_au_intensity(&mut set_b, 12, 1.0);
332        let frame_b = au_frame_from_set(&set_b, 1.0);
333
334        let blended = blend_au_frames(&frame_a, &frame_b, 0.5);
335        let au12 = blended.au_intensities.iter().find(|&&(code, _)| code == 12);
336        assert!(au12.is_some());
337        assert!((au12.expect("should succeed").1 - 0.5).abs() < 1e-6);
338        assert!((blended.time - 0.5).abs() < 1e-6);
339    }
340
341    #[test]
342    fn test_au_count() {
343        let set = standard_facs_set();
344        assert_eq!(au_count(&set), 15);
345    }
346}