feat: integrate basin force estimator (pre-force) for 7x12 sensor
This commit is contained in:
2
src-tauri/.cargo/config.toml
Normal file
2
src-tauri/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[registries.kellnr]
|
||||
index = "sparse+http://crates.huangyanjie.com/api/v1/crates/"
|
||||
49
src-tauri/Cargo.lock
generated
49
src-tauri/Cargo.lock
generated
@@ -14,6 +14,7 @@ dependencies = [
|
||||
"crc",
|
||||
"csv",
|
||||
"dirs",
|
||||
"eskin-finger-sdk",
|
||||
"fern",
|
||||
"futures-util",
|
||||
"humantime",
|
||||
@@ -1152,6 +1153,25 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eskin-finger-sdk"
|
||||
version = "0.1.0"
|
||||
source = "sparse+http://crates.huangyanjie.com/api/v1/crates/"
|
||||
checksum = "341d54dbc70a0fb7cdd04162cdda6ab5735f9a4f717b1921b42c00e8afc37bb9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-channel",
|
||||
"fern",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serialport",
|
||||
"thiserror 2.0.18",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
@@ -2314,9 +2334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -2340,6 +2360,26 @@ dependencies = [
|
||||
"redox_syscall 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
@@ -4263,6 +4303,7 @@ dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"io-kit-sys",
|
||||
"libudev",
|
||||
"mach2",
|
||||
"nix 0.26.4",
|
||||
"scopeguard",
|
||||
@@ -5565,9 +5606,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.22.0"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
||||
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
|
||||
@@ -49,10 +49,11 @@ crc = "3.4.0"
|
||||
axum = { version = "0.8", features = ["ws"] }
|
||||
tower-http = { version = "0.6", features = ["cors"] }
|
||||
futures-util = "0.3"
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
uuid = { version = "1.23", features = ["v4", "serde"] }
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
ndarray = { version = "0.15", optional = true }
|
||||
eskin-finger-sdk = { version = "0.1.0", registry = "kellnr" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-updater = "2"
|
||||
|
||||
1021
src-tauri/nsis/installer.nsi
Normal file
1021
src-tauri/nsis/installer.nsi
Normal file
File diff suppressed because it is too large
Load Diff
217
src-tauri/resources/model_params.json
Normal file
217
src-tauri/resources/model_params.json
Normal file
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"scaler_mean": [
|
||||
1748.7541486595198,
|
||||
1292.5704664084863,
|
||||
669.8700117864961,
|
||||
1617.8798712839798,
|
||||
2104.589811228976,
|
||||
3267.658809002638,
|
||||
3366.4000112252343,
|
||||
2660.981740285495,
|
||||
2656.615909898786,
|
||||
1747.1196048717518,
|
||||
3093.4178032216423,
|
||||
3107.599371386878,
|
||||
4138.929019101607,
|
||||
3778.3928270752654,
|
||||
3495.851920450506,
|
||||
3110.5580063983834,
|
||||
2310.8518456156107,
|
||||
2899.8918261585377,
|
||||
3286.6881442816784,
|
||||
3601.237076948981,
|
||||
2590.9553048586554,
|
||||
2555.2781425978933,
|
||||
2004.8764850049579,
|
||||
1333.8961665824775,
|
||||
2090.217507623805,
|
||||
0.363302046990876,
|
||||
0.2506597877765041,
|
||||
0.12741811820991292,
|
||||
0.32195020821212794,
|
||||
0.43317540002685884,
|
||||
0.7725988160553472,
|
||||
0.791227193907261,
|
||||
0.5957799875116326,
|
||||
0.5873844015441929,
|
||||
0.35855586659016336,
|
||||
0.7267512979672636,
|
||||
0.7214172326166498,
|
||||
1.0,
|
||||
0.9089476753706724,
|
||||
0.8226695360434777,
|
||||
0.7208819781157673,
|
||||
0.5152795489332506,
|
||||
0.6711736481838434,
|
||||
0.7782925265622518,
|
||||
0.8648282061576593,
|
||||
0.5787625095682526,
|
||||
0.5752349727514727,
|
||||
0.43456864805018935,
|
||||
0.27668525082454587,
|
||||
0.47414670304783574,
|
||||
4138.929019101607,
|
||||
64531.08183195824,
|
||||
175620.92531477427,
|
||||
22.847729696357412,
|
||||
14.671691561018095,
|
||||
0.07533558084489102,
|
||||
12446.865764906175,
|
||||
47945.287047950456,
|
||||
2.8973185436828195,
|
||||
10.774373017335268,
|
||||
3.472192991899253,
|
||||
-0.013941562889309035,
|
||||
0.09672681097411825,
|
||||
0.5067195499928454,
|
||||
0.755407246398865,
|
||||
0.03711810817384146,
|
||||
11.154421806888552,
|
||||
64500.8986854629
|
||||
],
|
||||
"scaler_scale": [
|
||||
1458.5456651154973,
|
||||
1319.8585484401115,
|
||||
798.8535944732339,
|
||||
1467.8233720347457,
|
||||
1637.8964913406842,
|
||||
1330.3349975112737,
|
||||
1391.430499849884,
|
||||
1444.166940848846,
|
||||
1630.948040054198,
|
||||
1406.2203759964518,
|
||||
1289.9699402243327,
|
||||
1442.0533616965101,
|
||||
1437.7214049715994,
|
||||
1393.522474091575,
|
||||
1468.6421185157626,
|
||||
1449.3479990930084,
|
||||
1293.2464048717598,
|
||||
1331.2560392843097,
|
||||
1326.1289536453178,
|
||||
1357.3405110533047,
|
||||
1452.4854193036483,
|
||||
1348.4425883366337,
|
||||
1318.1429721243371,
|
||||
1059.93845215709,
|
||||
1114.1647557935548,
|
||||
0.2395898634701691,
|
||||
0.21706962815914935,
|
||||
0.13523106483202163,
|
||||
0.23880331588910964,
|
||||
0.24830003478347082,
|
||||
0.1464527498295455,
|
||||
0.15391677914992113,
|
||||
0.18125664726966026,
|
||||
0.2326879002599809,
|
||||
0.23502163992653513,
|
||||
0.13026800431597335,
|
||||
0.15563022147466685,
|
||||
1.0,
|
||||
0.09922737602626737,
|
||||
0.18291931318098986,
|
||||
0.15401181704844932,
|
||||
0.2143892844194339,
|
||||
0.16856049162074294,
|
||||
0.15902500893917185,
|
||||
0.18285009098439925,
|
||||
0.17264751056304276,
|
||||
0.21090366624550771,
|
||||
0.16802111677577075,
|
||||
0.19264329284433157,
|
||||
0.19589977001187556,
|
||||
1437.7214049715994,
|
||||
32602.413979370118,
|
||||
95845.11969895993,
|
||||
3.426376344472427,
|
||||
3.408382770733738,
|
||||
0.033353666248921464,
|
||||
5505.629576226806,
|
||||
25703.01200969283,
|
||||
0.4599551450527747,
|
||||
2.978321440052941,
|
||||
0.3916581766443181,
|
||||
0.06096090153067211,
|
||||
0.07864618660494935,
|
||||
0.0344984508436715,
|
||||
0.17668176728315207,
|
||||
0.18905119470509504,
|
||||
5352.30503788098,
|
||||
32297.31796957845
|
||||
],
|
||||
"ridge_coef": [
|
||||
7.4424310127566695,
|
||||
13.345966730219576,
|
||||
2.351840055857306,
|
||||
6.088230738742203,
|
||||
-10.030964629299273,
|
||||
3.876136979406362,
|
||||
-11.251608537526174,
|
||||
16.84502390958064,
|
||||
-2.093552796584439,
|
||||
-5.784923711493545,
|
||||
-6.67830546424787,
|
||||
-4.654052249161928,
|
||||
6.038218458133514,
|
||||
9.82412450487401,
|
||||
-6.200667839175651,
|
||||
-0.3133364534713342,
|
||||
-8.75036029102127,
|
||||
12.785901861589027,
|
||||
-3.7296377182327123,
|
||||
6.546167384121816,
|
||||
-4.984129287282208,
|
||||
8.311396481777527,
|
||||
-0.6248790895663127,
|
||||
2.69008779623183,
|
||||
12.996047839696784,
|
||||
-2.2609944767610504,
|
||||
-5.131537716982507,
|
||||
0.3988922195665723,
|
||||
-5.197736884253156,
|
||||
4.556854888903703,
|
||||
-0.8642438099006351,
|
||||
6.327731485629085,
|
||||
-5.157281763422745,
|
||||
0.10691827520622764,
|
||||
4.656962972053113,
|
||||
3.2628870750114887,
|
||||
4.033159141354671,
|
||||
0.0,
|
||||
-2.9206404009765268,
|
||||
1.8683691849941264,
|
||||
2.408006875407745,
|
||||
7.250310827671452,
|
||||
-3.97015207422554,
|
||||
0.7316093212194048,
|
||||
-3.459346094204882,
|
||||
2.4407660203169255,
|
||||
-2.872982666400644,
|
||||
1.8797071977799857,
|
||||
-1.3374700235689694,
|
||||
-7.9533345474852295,
|
||||
6.038063637368508,
|
||||
1.615806581558555,
|
||||
95785.62883805836,
|
||||
0.12233606167692031,
|
||||
-0.1515900264871255,
|
||||
2.2023033069961873,
|
||||
8.776787743985668,
|
||||
-0.16714060634667535,
|
||||
-2.751671223554021,
|
||||
0.2511944267079865,
|
||||
6.13561607395193,
|
||||
2.85703108671782,
|
||||
-0.11255626089468472,
|
||||
-0.9017242341101542,
|
||||
-0.627291200283328,
|
||||
3.4664885582435883,
|
||||
0.02591345630626686,
|
||||
0.5530407299425606
|
||||
],
|
||||
"ridge_intercept": 175620.9253147744,
|
||||
"n_features": 68,
|
||||
"noise_threshold": 15.0,
|
||||
"contact_threshold": 20.0,
|
||||
"ema_alpha": 0.9
|
||||
}
|
||||
397
src-tauri/src/serial_core/basin_force_estimator.rs
Normal file
397
src-tauri/src/serial_core/basin_force_estimator.rs
Normal file
@@ -0,0 +1,397 @@
|
||||
//! 7×12 柔性压力点阵力估计 - Rust 实现
|
||||
//!
|
||||
//! 与 Python `basin_feature_extractor.py` 完全对齐。
|
||||
//! 内嵌 `model_params.json`,对每帧 7×12 传感器数据提取 68 维特征并用
|
||||
//! StandardScaler + Ridge 回归估计法向力 Fz。
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
// ───────────────── 常量 ─────────────────
|
||||
|
||||
const ROWS: usize = 7;
|
||||
const COLS: usize = 12;
|
||||
const ROI_RADIUS: usize = 2;
|
||||
const ROI_SIZE: usize = 2 * ROI_RADIUS + 1; // 5
|
||||
const N_FEATURES: usize = 68; // 25 + 25 + 18
|
||||
|
||||
// ───────────────── 模型参数 JSON(编译时嵌入)─────────────────
|
||||
|
||||
const MODEL_PARAMS_JSON: &str = include_str!("../../resources/model_params.json");
|
||||
|
||||
// ───────────────── 模型参数反序列化 ─────────────────
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ModelParams {
|
||||
scaler_mean: Vec<f64>,
|
||||
scaler_scale: Vec<f64>,
|
||||
ridge_coef: Vec<f64>,
|
||||
ridge_intercept: f64,
|
||||
n_features: usize,
|
||||
noise_threshold: f64,
|
||||
contact_threshold: f64,
|
||||
ema_alpha: f64,
|
||||
}
|
||||
|
||||
// ───────────────── 估算器 ─────────────────
|
||||
|
||||
pub struct BasinForceEstimator {
|
||||
// 模型参数
|
||||
scaler_mean: [f64; N_FEATURES],
|
||||
scaler_scale: [f64; N_FEATURES],
|
||||
ridge_coef: [f64; N_FEATURES],
|
||||
ridge_intercept: f64,
|
||||
// 超参数
|
||||
noise_threshold: f64,
|
||||
contact_threshold: f64,
|
||||
ema_alpha: f64,
|
||||
// 时序状态(需要可变)
|
||||
prev_roi_sum: f64,
|
||||
ema_sum: f64,
|
||||
first_frame: bool,
|
||||
}
|
||||
|
||||
impl BasinForceEstimator {
|
||||
/// 使用编译时内嵌的 model_params.json 创建估算器
|
||||
pub fn new() -> Self {
|
||||
Self::from_json_str(MODEL_PARAMS_JSON)
|
||||
.expect("内嵌 model_params.json 加载失败")
|
||||
}
|
||||
|
||||
pub fn from_json_str(json: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let p: ModelParams = serde_json::from_str(json)?;
|
||||
if p.n_features != N_FEATURES {
|
||||
return Err(format!(
|
||||
"模型特征维度不匹配: 期望 {}, 实际 {}",
|
||||
N_FEATURES, p.n_features
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let mut scaler_mean = [0.0; N_FEATURES];
|
||||
let mut scaler_scale = [0.0; N_FEATURES];
|
||||
let mut ridge_coef = [0.0; N_FEATURES];
|
||||
scaler_mean.copy_from_slice(&p.scaler_mean);
|
||||
scaler_scale.copy_from_slice(&p.scaler_scale);
|
||||
ridge_coef.copy_from_slice(&p.ridge_coef);
|
||||
|
||||
Ok(Self {
|
||||
scaler_mean,
|
||||
scaler_scale,
|
||||
ridge_coef,
|
||||
ridge_intercept: p.ridge_intercept,
|
||||
noise_threshold: p.noise_threshold,
|
||||
contact_threshold: p.contact_threshold,
|
||||
ema_alpha: p.ema_alpha,
|
||||
prev_roi_sum: 0.0,
|
||||
ema_sum: 0.0,
|
||||
first_frame: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.prev_roi_sum = 0.0;
|
||||
self.ema_sum = 0.0;
|
||||
self.first_frame = true;
|
||||
}
|
||||
|
||||
pub fn predict_frame(&mut self, frame: &[f64; 84]) -> f64 {
|
||||
let features = self.extract_features(frame);
|
||||
self.ridge_predict(&features)
|
||||
}
|
||||
|
||||
// ───────────── 特征提取 ─────────────
|
||||
|
||||
fn extract_features(&mut self, raw: &[f64; 84]) -> [f64; N_FEATURES] {
|
||||
let mut x = [[0.0f64; COLS]; ROWS];
|
||||
let mut max_value = 0.0f64;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
let v = raw[r * COLS + c].max(0.0);
|
||||
x[r][c] = v;
|
||||
if v > max_value {
|
||||
max_value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if max_value < self.contact_threshold {
|
||||
self.update_temporal(0.0);
|
||||
return [0.0; N_FEATURES];
|
||||
}
|
||||
|
||||
let mut peak_row = 0usize;
|
||||
let mut peak_col = 0usize;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
if x[r][c] >= x[peak_row][peak_col] {
|
||||
peak_row = r;
|
||||
peak_col = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let roi = self.extract_roi(&x, peak_row, peak_col);
|
||||
self.compute_features(&x, &roi, max_value, peak_row, peak_col)
|
||||
}
|
||||
|
||||
fn extract_roi(
|
||||
&self,
|
||||
x: &[[f64; COLS]; ROWS],
|
||||
pr: usize,
|
||||
pc: usize,
|
||||
) -> [[f64; ROI_SIZE]; ROI_SIZE] {
|
||||
let r = ROI_RADIUS as isize;
|
||||
let mut roi = [[0.0f64; ROI_SIZE]; ROI_SIZE];
|
||||
|
||||
let r_start = (pr as isize - r).max(0) as usize;
|
||||
let r_end = (pr + ROI_RADIUS + 1).min(ROWS);
|
||||
let c_start = (pc as isize - r).max(0) as usize;
|
||||
let c_end = (pc + ROI_RADIUS + 1).min(COLS);
|
||||
|
||||
let roi_r_start = (r_start as isize - (pr as isize - r)).max(0) as usize;
|
||||
let roi_c_start = (c_start as isize - (pc as isize - r)).max(0) as usize;
|
||||
|
||||
for (i, ri) in (r_start..r_end).enumerate() {
|
||||
for (j, ci) in (c_start..c_end).enumerate() {
|
||||
roi[roi_r_start + i][roi_c_start + j] = x[ri][ci];
|
||||
}
|
||||
}
|
||||
roi
|
||||
}
|
||||
|
||||
fn compute_features(
|
||||
&mut self,
|
||||
x: &[[f64; COLS]; ROWS],
|
||||
roi: &[[f64; ROI_SIZE]; ROI_SIZE],
|
||||
max_value: f64,
|
||||
peak_row: usize,
|
||||
peak_col: usize,
|
||||
) -> [f64; N_FEATURES] {
|
||||
let center = ROI_RADIUS;
|
||||
let mut feat = [0.0f64; N_FEATURES];
|
||||
let mut idx = 0;
|
||||
|
||||
// ROI 原始值 (25维)
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
feat[idx] = roi[r][c];
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ROI 归一化形状 (25维)
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
feat[idx] = if max_value > 0.0 {
|
||||
roi[r][c] / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// roi_sum, global_sum
|
||||
let mut roi_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
roi_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let mut global_sum = 0.0f64;
|
||||
for r in 0..ROWS {
|
||||
for c in 0..COLS {
|
||||
global_sum += x[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
// active_area
|
||||
let thr = self.noise_threshold.max(0.05 * max_value);
|
||||
let mut active_area = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
if roi[r][c] > thr {
|
||||
active_area += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let participation = if max_value > 0.0 {
|
||||
roi_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let concentration = if roi_sum > 0.0 {
|
||||
max_value / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// ring1_sum (上下左右4点)
|
||||
let ring1_positions = [
|
||||
(center - 1, center),
|
||||
(center + 1, center),
|
||||
(center, center - 1),
|
||||
(center, center + 1),
|
||||
];
|
||||
let ring1_sum: f64 = ring1_positions.iter().map(|&(r, c)| roi[r][c]).sum();
|
||||
|
||||
// ring2_sum (除中心和ring1外)
|
||||
let mut ring2_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
if (r, c) == (center, center) {
|
||||
continue;
|
||||
}
|
||||
if ring1_positions.contains(&(r, c)) {
|
||||
continue;
|
||||
}
|
||||
ring2_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
let ring1_ratio = if max_value > 0.0 {
|
||||
ring1_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let ring2_ratio = if max_value > 0.0 {
|
||||
ring2_sum / max_value
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// spread
|
||||
let spread = if roi_sum > 0.0 {
|
||||
let mut s = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
let dr = r as f64 - center as f64;
|
||||
let dc = c as f64 - center as f64;
|
||||
s += (dr * dr + dc * dc) * roi[r][c];
|
||||
}
|
||||
}
|
||||
s / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// asym_x
|
||||
let mut left_sum = 0.0f64;
|
||||
let mut right_sum = 0.0f64;
|
||||
for r in 0..ROI_SIZE {
|
||||
for c in 0..center {
|
||||
left_sum += roi[r][c];
|
||||
}
|
||||
for c in (center + 1)..ROI_SIZE {
|
||||
right_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let asym_x = if roi_sum > 0.0 {
|
||||
(right_sum - left_sum) / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// asym_y
|
||||
let mut up_sum = 0.0f64;
|
||||
let mut down_sum = 0.0f64;
|
||||
for r in 0..center {
|
||||
for c in 0..ROI_SIZE {
|
||||
up_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
for r in (center + 1)..ROI_SIZE {
|
||||
for c in 0..ROI_SIZE {
|
||||
down_sum += roi[r][c];
|
||||
}
|
||||
}
|
||||
let asym_y = if roi_sum > 0.0 {
|
||||
(down_sum - up_sum) / roi_sum
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// 位置
|
||||
let peak_row_norm = peak_row as f64 / (ROWS - 1) as f64;
|
||||
let peak_col_norm = peak_col as f64 / (COLS - 1) as f64;
|
||||
|
||||
// near_edge
|
||||
let r = ROI_RADIUS as isize;
|
||||
let near_edge = if (peak_row as isize) < r
|
||||
|| peak_row >= ROWS - ROI_RADIUS
|
||||
|| (peak_col as isize) < r
|
||||
|| peak_col >= COLS - ROI_RADIUS
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// 时序特征
|
||||
let delta_sum = roi_sum - self.prev_roi_sum;
|
||||
if self.first_frame {
|
||||
self.ema_sum = roi_sum;
|
||||
self.first_frame = false;
|
||||
} else {
|
||||
self.ema_sum = self.ema_alpha * self.ema_sum + (1.0 - self.ema_alpha) * roi_sum;
|
||||
}
|
||||
self.prev_roi_sum = roi_sum;
|
||||
|
||||
let scalars = [
|
||||
max_value,
|
||||
roi_sum,
|
||||
global_sum,
|
||||
active_area,
|
||||
participation,
|
||||
concentration,
|
||||
ring1_sum,
|
||||
ring2_sum,
|
||||
ring1_ratio,
|
||||
ring2_ratio,
|
||||
spread,
|
||||
asym_x,
|
||||
asym_y,
|
||||
peak_row_norm,
|
||||
peak_col_norm,
|
||||
near_edge,
|
||||
delta_sum,
|
||||
self.ema_sum,
|
||||
];
|
||||
for &v in &scalars {
|
||||
feat[idx] = v;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
debug_assert_eq!(idx, N_FEATURES);
|
||||
feat
|
||||
}
|
||||
|
||||
fn update_temporal(&mut self, roi_sum: f64) {
|
||||
self.prev_roi_sum = roi_sum;
|
||||
if self.first_frame {
|
||||
self.ema_sum = roi_sum;
|
||||
self.first_frame = false;
|
||||
} else {
|
||||
self.ema_sum = self.ema_alpha * self.ema_sum + (1.0 - self.ema_alpha) * roi_sum;
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────── 推理 ─────────────
|
||||
|
||||
fn ridge_predict(&self, features: &[f64; N_FEATURES]) -> f64 {
|
||||
let mut scaled = [0.0f64; N_FEATURES];
|
||||
for i in 0..N_FEATURES {
|
||||
let s = self.scaler_scale[i];
|
||||
scaled[i] = if s.abs() > 1e-12 {
|
||||
(features[i] - self.scaler_mean[i]) / s
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
}
|
||||
let mut y = self.ridge_intercept;
|
||||
for i in 0..N_FEATURES {
|
||||
y += self.ridge_coef[i] * scaled[i];
|
||||
}
|
||||
y
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use async_trait::async_trait;
|
||||
use csv::StringRecord;
|
||||
use anyhow::anyhow;
|
||||
use std::io::Read;
|
||||
use log::debug;
|
||||
use log::{debug, info};
|
||||
|
||||
const FRAME_BUFFER_MIN_LENGTH: usize = 15;
|
||||
|
||||
@@ -226,6 +226,7 @@ impl Codec<TactileAFrame> for TactileACodec {
|
||||
req_bytes.extend_from_slice((f.meta.except_data_len as u16).to_le_bytes().as_slice());
|
||||
let checksum = calc_crc8_itu(req_bytes.as_slice());
|
||||
req_bytes.push(checksum);
|
||||
info!("send: {:02X?}", req_bytes);
|
||||
Ok(req_bytes)
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod record;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "multi-dim")]
|
||||
pub mod multi_dim_force;
|
||||
pub mod basin_force_estimator;
|
||||
|
||||
pub type TestRecording = Recording<TestFrame>;
|
||||
pub type TactileARecording = Recording<TactileAFrame>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::serial_core::basin_force_estimator::BasinForceEstimator;
|
||||
use crate::serial_core::codec::Codec;
|
||||
use crate::serial_core::codecs::tactile_a::TactileACodec;
|
||||
use crate::serial_core::frame::{FrameHandler, TactileAFrame, TestFrame};
|
||||
@@ -233,6 +234,7 @@ where
|
||||
let mut prune_interval = time::interval(Duration::from_millis(450));
|
||||
#[cfg(feature = "multi-dim")]
|
||||
let mut pzt_processor = PztProcessor::new();
|
||||
let mut force_estimator = BasinForceEstimator::new();
|
||||
let mut pending_sub_frame: Option<PendingSubFrame<F>> = None;
|
||||
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
@@ -309,6 +311,16 @@ where
|
||||
drop(record);
|
||||
|
||||
if let Some(vals) = decode_res {
|
||||
// Basin force estimation (pre-force)
|
||||
if vals.len() == 84 {
|
||||
let mut frame_f64 = [0.0f64; 84];
|
||||
for (i, v) in vals.iter().enumerate() {
|
||||
frame_f64[i] = *v as f64;
|
||||
}
|
||||
let pre_force = force_estimator.predict_frame(&frame_f64);
|
||||
debug!("pre-force: {:.2}", pre_force);
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi-dim")]
|
||||
{
|
||||
let pzt_values = vals.iter().map(|value| *value as f32).collect::<Vec<f32>>();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": true,
|
||||
"createUpdaterArtifacts": false,
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
|
||||
Reference in New Issue
Block a user