Skip to main content

oxihuman_morph/
expression_retarget_ml.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! ML-based expression retargeting adapter stub.
6
7/// Mapping from a source expression weight to a target expression weight.
8#[derive(Debug, Clone)]
9pub struct RetargetMapping {
10    pub source_idx: usize,
11    pub target_idx: usize,
12    pub gain: f32,
13    pub offset: f32,
14}
15
16/// ML expression retargeting adapter.
17#[derive(Debug, Clone)]
18pub struct ExpressionRetargetMl {
19    pub mappings: Vec<RetargetMapping>,
20    pub source_dim: usize,
21    pub target_dim: usize,
22    pub enabled: bool,
23}
24
25impl ExpressionRetargetMl {
26    pub fn new(source_dim: usize, target_dim: usize) -> Self {
27        ExpressionRetargetMl {
28            mappings: Vec::new(),
29            source_dim,
30            target_dim,
31            enabled: true,
32        }
33    }
34}
35
36/// Create a new ML expression retargeting adapter.
37pub fn new_expression_retarget_ml(source_dim: usize, target_dim: usize) -> ExpressionRetargetMl {
38    ExpressionRetargetMl::new(source_dim, target_dim)
39}
40
41/// Add a mapping rule.
42pub fn erml_add_mapping(adapter: &mut ExpressionRetargetMl, mapping: RetargetMapping) {
43    adapter.mappings.push(mapping);
44}
45
46/// Retarget source weights to target weights (stub: zeroed output).
47pub fn erml_retarget(adapter: &ExpressionRetargetMl, source: &[f32]) -> Vec<f32> {
48    /* Stub: applies linear mappings from source to target */
49    let mut out = vec![0.0f32; adapter.target_dim];
50    for m in &adapter.mappings {
51        if m.source_idx < source.len() && m.target_idx < out.len() {
52            out[m.target_idx] += source[m.source_idx] * m.gain + m.offset;
53        }
54    }
55    out
56}
57
58/// Return mapping count.
59pub fn erml_mapping_count(adapter: &ExpressionRetargetMl) -> usize {
60    adapter.mappings.len()
61}
62
63/// Enable or disable.
64pub fn erml_set_enabled(adapter: &mut ExpressionRetargetMl, enabled: bool) {
65    adapter.enabled = enabled;
66}
67
68/// Serialize to JSON-like string.
69pub fn erml_to_json(adapter: &ExpressionRetargetMl) -> String {
70    format!(
71        r#"{{"source_dim":{},"target_dim":{},"mappings":{},"enabled":{}}}"#,
72        adapter.source_dim,
73        adapter.target_dim,
74        adapter.mappings.len(),
75        adapter.enabled
76    )
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_new_dims() {
85        let a = new_expression_retarget_ml(10, 15);
86        assert_eq!(a.source_dim, 10 /* source_dim must match */,);
87        assert_eq!(a.target_dim, 15 /* target_dim must match */,);
88    }
89
90    #[test]
91    fn test_no_mappings_initially() {
92        let a = new_expression_retarget_ml(5, 5);
93        assert_eq!(erml_mapping_count(&a), 0 /* no mappings initially */,);
94    }
95
96    #[test]
97    fn test_add_mapping() {
98        let mut a = new_expression_retarget_ml(5, 5);
99        erml_add_mapping(
100            &mut a,
101            RetargetMapping {
102                source_idx: 0,
103                target_idx: 0,
104                gain: 1.0,
105                offset: 0.0,
106            },
107        );
108        assert_eq!(erml_mapping_count(&a), 1 /* one mapping after add */,);
109    }
110
111    #[test]
112    fn test_retarget_output_length() {
113        let a = new_expression_retarget_ml(4, 6);
114        let out = erml_retarget(&a, &[0.5; 4]);
115        assert_eq!(out.len(), 6 /* output length must match target_dim */,);
116    }
117
118    #[test]
119    fn test_retarget_with_gain() {
120        let mut a = new_expression_retarget_ml(2, 2);
121        erml_add_mapping(
122            &mut a,
123            RetargetMapping {
124                source_idx: 0,
125                target_idx: 0,
126                gain: 2.0,
127                offset: 0.0,
128            },
129        );
130        let out = erml_retarget(&a, &[0.5, 0.0]);
131        assert!((out[0] - 1.0).abs() < 1e-5, /* gain of 2 on 0.5 must yield 1.0 */);
132    }
133
134    #[test]
135    fn test_retarget_with_offset() {
136        let mut a = new_expression_retarget_ml(2, 2);
137        erml_add_mapping(
138            &mut a,
139            RetargetMapping {
140                source_idx: 0,
141                target_idx: 0,
142                gain: 0.0,
143                offset: 0.3,
144            },
145        );
146        let out = erml_retarget(&a, &[0.0, 0.0]);
147        assert!((out[0] - 0.3).abs() < 1e-5, /* offset alone must appear in output */);
148    }
149
150    #[test]
151    fn test_set_enabled() {
152        let mut a = new_expression_retarget_ml(2, 2);
153        erml_set_enabled(&mut a, false);
154        assert!(!a.enabled /* must be disabled */,);
155    }
156
157    #[test]
158    fn test_to_json_contains_dims() {
159        let a = new_expression_retarget_ml(3, 4);
160        let j = erml_to_json(&a);
161        assert!(j.contains("\"source_dim\""), /* json must contain source_dim */);
162    }
163
164    #[test]
165    fn test_enabled_default() {
166        let a = new_expression_retarget_ml(1, 1);
167        assert!(a.enabled /* must be enabled by default */,);
168    }
169
170    #[test]
171    fn test_retarget_zeroed_without_mappings() {
172        let a = new_expression_retarget_ml(3, 3);
173        let out = erml_retarget(&a, &[1.0, 1.0, 1.0]);
174        assert!(out.iter().all(|&v| v.abs() < 1e-6), /* no mappings means zero output */);
175    }
176}