synthda-demo / scripts /smoothing.test.mjs
evelynsaltt's picture
Add interactive SynthDA motion showcase
f108d90 verified
Raw History Blame Contribute Delete
2.46 kB
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {smoothMotion,smoothSequence,samplePose} from '../smoothing.js';
import {boundsFor,renderStage} from '../motion.js';
const raw=JSON.parse(fs.readFileSync(new URL('../assets/data/fall-real-synthetic-2.json',import.meta.url)));
const acceleration=seq=>{let sum=0,n=0;for(let f=75;f<seq.length-1;f++)for(let j=0;j<22;j++)for(let c=0;c<3;c++){sum+=(seq[f+1][j][c]-2*seq[f][j][c]+seq[f-1][j][c])**2;n++;}return Math.sqrt(sum/n);};
test('Smoothing reduces late-frame jitter without changing saved data or frame count',()=>{
const before=JSON.stringify(raw),smooth=smoothMotion(raw);
assert.equal(JSON.stringify(raw),before);
assert.equal(smooth.sourceA.length,raw.sourceA.length);
for(const key of ['1','5','9'])assert.ok(acceleration(smooth.variants[key])<acceleration(raw.variants[key])*.3);
for(let f=0;f<raw.sourceA.length;f++)for(let j=0;j<22;j++)for(let c=0;c<3;c++){
const neighbors=raw.sourceA.slice(Math.max(0,f-4),f+5).map(p=>p[j][c]);
assert.ok(smooth.sourceA[f][j][c]>=Math.min(...neighbors)-1e-12&&smooth.sourceA[f][j][c]<=Math.max(...neighbors)+1e-12);
}
});
test('Filter preserves constant motion and subframe sampling interpolates safely',()=>{
const constant=Array.from({length:9},()=>[[1,2,3]]);
for(const frame of smoothSequence(constant))frame[0].forEach((v,i)=>assert.ok(Math.abs(v-(i+1))<1e-12));
assert.deepEqual(samplePose([[[0,0,0]],[[2,4,6]]],.5),[[1,2,3]]);
assert.deepEqual(samplePose([[[0,0,0]],[[2,4,6]]],5),[[2,4,6]]);
});
test('Mannequin and skeleton overlay render finite geometry through late fall frames',()=>{
let fills=0;const context=new Proxy({},{get:(t,k)=>t[k]??((...args)=>{for(const arg of args)if(typeof arg==='number')assert.ok(Number.isFinite(arg),k);if(k==='fill')fills++;if(k==='createLinearGradient')return{addColorStop(){}};}),set:(t,k,v)=>(t[k]=v,true)});
const canvas={clientWidth:360,clientHeight:244,getContext:()=>context};
for(const frame of [0,74,90.5,130])for(const yaw of [-90,20,90])for(const skeleton of [false,true])renderStage(canvas,[{key:'sourceA',sequence:raw.sourceA,color:'#587997'}],raw.parents,frame,yaw,1,boundsFor(raw,yaw),{human:true,skeleton});
assert.ok(fills>100);
});
console.log('Example 2, frames 76–130: second-difference RMS',JSON.stringify({original:acceleration(raw.variants['5']),smoothed:acceleration(smoothMotion(raw).variants['5'])}));