Sanket-Setu / frontend /src /lib /landmarkUtils.ts
devrajsinh2012's picture
fix: model paths (.pth), landmark normalization, WS URL, GPU fallback; add ModelSelector; mobile layout improvements
c476eae
/**
* Landmark utility functions — mirrors the backend preprocessing.
*
* The XGBoost model (Pipeline A) was trained on MediaPipe landmarks that are
* already normalised to [0,1] relative to the image frame. The simplest
* preprocessing that makes the model translation-invariant is to subtract the
* wrist landmark (index 0) so every coordinate is relative to the wrist.
*
* ⚠️ If your training notebook used a different normalisation (e.g. min-max
* scaling or only x/y without z), update normaliseLandmarks() to match.
*/
/** MediaPipe Hands result landmark shape */
export interface RawLandmark {
x: number;
y: number;
z: number;
}
/**
* Convert MediaPipe NormalizedLandmark[] (21 points) to a flat 63-element
* array of raw [0,1]-normalised coordinates as expected by the trained models.
*
* The XGBoost, Autoencoder+LGBM models were trained directly on the raw
* MediaPipe landmark coordinates (x, y, z per landmark, no wrist-centering).
* Sending wrist-subtracted coords produces incorrect predictions.
*/
export function normaliseLandmarks(raw: RawLandmark[]): number[] {
if (raw.length !== 21) {
throw new Error(`Expected 21 landmarks, got ${raw.length}`);
}
const flat: number[] = [];
for (const lm of raw) {
flat.push(lm.x, lm.y, lm.z);
}
return flat; // length 63
}
/**
* MediaPipe hand topology — pairs of landmark indices that form bones.
* Used by LandmarkCanvas to draw connections between landmarks.
*/
export const HAND_CONNECTIONS: [number, number][] = [
// Palm
[0, 1], [1, 2], [2, 3], [3, 4], // Thumb
[0, 5], [5, 6], [6, 7], [7, 8], // Index
[5, 9], [9, 10], [10, 11], [11, 12], // Middle
[9, 13], [13, 14], [14, 15], [15, 16], // Ring
[13, 17], [17, 18], [18, 19], [19, 20], // Pinky
[0, 17], // Wrist–pinky base
];