UNPKG

@glyph-cat/ml-helpers

Version:

Helper functions to use in conjunction with machine learning outputs such as mediapipe.

2 lines (1 loc) 14.4 kB
"use strict";var t,s,e,o,r,i,n,p=require("@glyph-cat/swiss-army-knife"),x=require("@mediapipe/tasks-vision"),c=require("cotton-box");exports.VisionAnalyzerState=void 0,(t=exports.VisionAnalyzerState||(exports.VisionAnalyzerState={}))[t.CREATED=0]="CREATED",t[t.INITIALIZING=1]="INITIALIZING",t[t.STANDBY=2]="STANDBY",t[t.ACTIVE=3]="ACTIVE",t[t.DISPOSED=4]="DISPOSED";class a{static t=!1;static o="/mediapipe/wasm";static get(){return this.o}static set(t){if(this.t)throw new Error("WASM path cannot be changed because the fileset has been loaded");this.o=t}}exports.DetectionMethod=void 0,(s=exports.DetectionMethod||(exports.DetectionMethod={})).detectForVideo="detectForVideo",s.recognizeForVideo="recognizeForVideo";class h{videoElement;getTaskRunner;detectionMethodName;static i=new p.LazyValue(()=>(a.t=!0,x.FilesetResolver.forVisionTasks(a.get())));static async getVision(){return this.i.value}taskRunner;lastRequestedAnimationFrame;result;state;constructor(t,s,e,o,r,i){this.videoElement=t,this.getTaskRunner=e,this.detectionMethodName=o,this.initialize=this.initialize.bind(this),this.start=this.start.bind(this),this.stop=this.stop.bind(this),this.performAnalysis=this.performAnalysis.bind(this),this.getProcessedResult=this.getProcessedResult.bind(this),this.dispose=this.dispose.bind(this),this.result=new c.SimpleStateManager(s),this.state=new c.SimpleFiniteStateManager(exports.VisionAnalyzerState.CREATED,[[exports.VisionAnalyzerState.CREATED,exports.VisionAnalyzerState.INITIALIZING],[exports.VisionAnalyzerState.CREATED,exports.VisionAnalyzerState.DISPOSED],[exports.VisionAnalyzerState.INITIALIZING,exports.VisionAnalyzerState.STANDBY],[exports.VisionAnalyzerState.STANDBY,exports.VisionAnalyzerState.ACTIVE],[exports.VisionAnalyzerState.STANDBY,exports.VisionAnalyzerState.DISPOSED],[exports.VisionAnalyzerState.ACTIVE,exports.VisionAnalyzerState.STANDBY],[exports.VisionAnalyzerState.ACTIVE,exports.VisionAnalyzerState.DISPOSED]],{name:r,serializeState:p.createEnumToStringConverter(exports.VisionAnalyzerState)}),i?.initializeImmediately&&this.initialize()}async initialize(){this.state.set(exports.VisionAnalyzerState.INITIALIZING),this.taskRunner=await this.getTaskRunner(),this.state.set(t=>t===exports.VisionAnalyzerState.INITIALIZING?exports.VisionAnalyzerState.STANDBY:t)}async start(){let t=!1;this.state.get()===exports.VisionAnalyzerState.INITIALIZING&&(t=!0,await this.state.wait(t=>t>exports.VisionAnalyzerState.INITIALIZING)),t&&this.state.get()!==exports.VisionAnalyzerState.STANDBY||(this.state.set(exports.VisionAnalyzerState.ACTIVE),this.lastRequestedAnimationFrame=requestAnimationFrame(this.performAnalysis))}async stop(){await this.state.wait(t=>t===exports.VisionAnalyzerState.CREATED||t>exports.VisionAnalyzerState.STANDBY),this.p(),this.state.get()!==exports.VisionAnalyzerState.DISPOSED&&this.state.set(exports.VisionAnalyzerState.STANDBY)}async dispose(){await this.state.wait(t=>t!==exports.VisionAnalyzerState.INITIALIZING),cancelAnimationFrame(this.lastRequestedAnimationFrame),this.result.dispose(),this.state.trySet(exports.VisionAnalyzerState.DISPOSED),this.state.dispose()}p(){cancelAnimationFrame(this.lastRequestedAnimationFrame)}async performAnalysis(){if(this.state.get()!==exports.VisionAnalyzerState.ACTIVE)return;const t=this.taskRunner[this.detectionMethodName](this.videoElement,performance.now()),s=this.getProcessedResult(t);s&&this.result.set(s),this.lastRequestedAnimationFrame=requestAnimationFrame(this.performAnalysis)}}class I extends h{constructor(t,s,e,o,r){super(t,s,e,exports.DetectionMethod.detectForVideo,o,r)}}class E extends I{options;static h={};constructor(t,s,e){super(t,new Array(Object.keys(exports.BodyPoseLandmark).length/2).fill({x:0,y:0,z:0,visibility:0}),async()=>{const t=JSON.stringify(s);return E.h[t]||(E.h[t]=x.PoseLandmarker.createFromOptions(await I.getVision(),{...s,numPoses:1})),E.h[t]},"OnePersonBodyPoseAnalyzer",e),this.options=e}getProcessedResult(t){const s=[];if(t.landmarks.length<=0)return;const e=t.landmarks[0],o=this.options?.flipHorizontally;for(const t of e)s.push({...t,x:o?p.reflect1D(t.x,.5):t.x,visibility:1});return s}}exports.BodyPoseLandmark=void 0,(e=exports.BodyPoseLandmark||(exports.BodyPoseLandmark={}))[e.NOSE=0]="NOSE",e[e.LEFT_EYE_INNER=1]="LEFT_EYE_INNER",e[e.LEFT_EYE=2]="LEFT_EYE",e[e.LEFT_EYE_OUTER=3]="LEFT_EYE_OUTER",e[e.RIGHT_EYE_INNER=4]="RIGHT_EYE_INNER",e[e.RIGHT_EYE=5]="RIGHT_EYE",e[e.RIGHT_EYE_OUTER=6]="RIGHT_EYE_OUTER",e[e.LEFT_EAR=7]="LEFT_EAR",e[e.RIGHT_EAR=8]="RIGHT_EAR",e[e.MOUTH_LEFT=9]="MOUTH_LEFT",e[e.MOUTH_RIGHT=10]="MOUTH_RIGHT",e[e.LEFT_SHOULDER=11]="LEFT_SHOULDER",e[e.RIGHT_SHOULDER=12]="RIGHT_SHOULDER",e[e.LEFT_ELBOW=13]="LEFT_ELBOW",e[e.RIGHT_ELBOW=14]="RIGHT_ELBOW",e[e.LEFT_WRIST=15]="LEFT_WRIST",e[e.RIGHT_WRIST=16]="RIGHT_WRIST",e[e.LEFT_PINKY=17]="LEFT_PINKY",e[e.RIGHT_PINKY=18]="RIGHT_PINKY",e[e.LEFT_INDEX=19]="LEFT_INDEX",e[e.RIGHT_INDEX=20]="RIGHT_INDEX",e[e.LEFT_THUMB=21]="LEFT_THUMB",e[e.RIGHT_THUMB=22]="RIGHT_THUMB",e[e.LEFT_HIP=23]="LEFT_HIP",e[e.RIGHT_HIP=24]="RIGHT_HIP",e[e.LEFT_KNEE=25]="LEFT_KNEE",e[e.RIGHT_KNEE=26]="RIGHT_KNEE",e[e.LEFT_ANKLE=27]="LEFT_ANKLE",e[e.RIGHT_ANKLE=28]="RIGHT_ANKLE",e[e.LEFT_HEEL=29]="LEFT_HEEL",e[e.RIGHT_HEEL=30]="RIGHT_HEEL",e[e.LEFT_FOOT_INDEX=31]="LEFT_FOOT_INDEX",e[e.RIGHT_FOOT_INDEX=32]="RIGHT_FOOT_INDEX";class _ extends I{options;static h={};constructor(t,s,e){super(t,new Array(478).fill({x:0,y:0,z:0,visibility:0}),async()=>{const t=JSON.stringify(s);return _.h[t]||(_.h[t]=x.FaceLandmarker.createFromOptions(await I.getVision(),{...s,numFaces:1})),_.h[t]},"OnePersonFaceMeshAnalyzer",e),this.options=e}getProcessedResult(t){const s=[];if(t.faceLandmarks?.length<=0)return;const e=this.options?.flipHorizontally,o=t.faceLandmarks[0];for(const t of o)s.push({...t,x:e?p.reflect1D(t.x,.5):t.x,visibility:1});return s}}function u(t,s,e){return p.getDistance2DByCoordinates(t,s)<p.getDistance2DByCoordinates(t,e)?"L":"R"}class T extends I{bodyPoseAnalyzer;options;static h={};constructor(t,s,e){super(t.videoElement,{},async()=>{const t=JSON.stringify(s);return T.h[t]||(T.h[t]=x.HandLandmarker.createFromOptions(await I.getVision(),{...s,numHands:2})),T.h[t]},"OnePersonHandPoseAnalyzer",e),this.bodyPoseAnalyzer=t,this.options=e}getProcessedResult(t){const s=this.bodyPoseAnalyzer.result.get(),e={},o=this.options?.flipHorizontally;for(const r of t.landmarks){const t=[];let i;for(let s=0;s<r.length;s++){const e=r[s],n={...e,x:o?p.reflect1D(e.x,.5):e.x,visibility:1};s===exports.HandPoseLandmark.WRIST&&(i=n),t.push(n)}e[u(i,s[exports.BodyPoseLandmark.LEFT_WRIST],s[exports.BodyPoseLandmark.RIGHT_WRIST])]=t}return e}}exports.HandPoseLandmark=void 0,(o=exports.HandPoseLandmark||(exports.HandPoseLandmark={}))[o.WRIST=0]="WRIST",o[o.THUMB_CMC=1]="THUMB_CMC",o[o.THUMB_MCP=2]="THUMB_MCP",o[o.THUMB_IP=3]="THUMB_IP",o[o.THUMB_TIP=4]="THUMB_TIP",o[o.INDEX_FINGER_MCP=5]="INDEX_FINGER_MCP",o[o.INDEX_FINGER_PIP=6]="INDEX_FINGER_PIP",o[o.INDEX_FINGER_DIP=7]="INDEX_FINGER_DIP",o[o.INDEX_FINGER_TIP=8]="INDEX_FINGER_TIP",o[o.MIDDLE_FINGER_MCP=9]="MIDDLE_FINGER_MCP",o[o.MIDDLE_FINGER_PIP=10]="MIDDLE_FINGER_PIP",o[o.MIDDLE_FINGER_DIP=11]="MIDDLE_FINGER_DIP",o[o.MIDDLE_FINGER_TIP=12]="MIDDLE_FINGER_TIP",o[o.RING_FINGER_MCP=13]="RING_FINGER_MCP",o[o.RING_FINGER_PIP=14]="RING_FINGER_PIP",o[o.RING_FINGER_DIP=15]="RING_FINGER_DIP",o[o.RING_FINGER_TIP=16]="RING_FINGER_TIP",o[o.PINKY_FINGER_MCP=17]="PINKY_FINGER_MCP",o[o.PINKY_FINGER_PIP=18]="PINKY_FINGER_PIP",o[o.PINKY_FINGER_DIP=19]="PINKY_FINGER_DIP",o[o.PINKY_FINGER_TIP=20]="PINKY_FINGER_TIP";class l extends h{bodyPoseAnalyzer;static h={};constructor(t,s,e){super(t.videoElement,{},async()=>{const t=JSON.stringify(s);return l.h[t]||(l.h[t]=x.GestureRecognizer.createFromOptions(await h.getVision(),{...s,numHands:2})),l.h[t]},exports.DetectionMethod.recognizeForVideo,"OnePersonHandGestureAnalyzer",e),this.bodyPoseAnalyzer=t}getProcessedResult(t){const s=this.bodyPoseAnalyzer.result.get(),e={};for(const o in t.landmarks){const r=t.landmarks[o],i=[];let n;for(let t=0;t<r.length;t++){const s=r[t],e={...s,x:p.reflect1D(s.x,.5),visibility:1};t===exports.HandPoseLandmark.WRIST&&(n=e),i.push(e)}e[u(n,s[exports.BodyPoseLandmark.LEFT_WRIST],s[exports.BodyPoseLandmark.RIGHT_WRIST])]=[t.gestures[o][0].categoryName??exports.HandGesture.NONE,i]}return e}}exports.HandGesture=void 0,(r=exports.HandGesture||(exports.HandGesture={})).NONE="None",r.CLOSED_FIST="Closed_Fist",r.OPEN_PALM="Open_Palm",r.POINTING_UP="Pointing_Up",r.THUMB_DOWN="Thumb_Down",r.THUMB_UP="Thumb_Up",r.VICTORY="Victory",r.I_LOVE_YOU="ILoveYou",exports.Finger=void 0,(i=exports.Finger||(exports.Finger={})).THUMB="T",i.INDEX="I",i.MIDDLE="M",i.RING="R",i.PINKY="P",p.fullyEnumerate(exports.Finger),exports.FingerCurl=void 0,(n=exports.FingerCurl||(exports.FingerCurl={}))[n.STRAIGHT=1]="STRAIGHT",n[n.HALF=2]="HALF",n[n.FULL=3]="FULL";class R{static getFingerCurlAngles(t,s){const e=P[s],o=[];for(let s=0;s<e.length-2;s++){const r=t[e[s]],i=t[e[s+1]],n=t[e[s+2]];o.push(p.getAngleFromPointsIn3D(r,i,n,i))}return o}static determineFingerCurl(t,s){const e=new p.NumericDataSet(this.getFingerCurlAngles(t,s));return s!==exports.Finger.THUMB&&e.mean>=f||e.mean>=N?exports.FingerCurl.STRAIGHT:function(t,s,e){for(let o=0;o<t.length;o++)if(Math.abs(t[o]-s[o])>e)return!1;return!0}(e.values,F[s],10)?exports.FingerCurl.FULL:exports.FingerCurl.HALF}I;constructor(t){const s={};for(const e in t){const o=t[e];p.hasProperty(o,"is")?s[e]={curlStates:new Set([o.is]),is:!0}:p.hasProperty(o,"isOneOf")?s[e]={curlStates:new Set([...o.isOneOf]),is:!0}:p.hasProperty(o,"isNot")?s[e]={curlStates:new Set([o.isNot]),is:!1}:p.hasProperty(o,"isNotOneOf")&&(s[e]={curlStates:new Set([...o.isNotOneOf]),is:!1})}this.I=s}isMatchedBy(t){for(const s in this.I){if(!p.hasProperty(this.I,s))continue;const e=R.determineFingerCurl(t,s),o=this.I[s];if(o?.is){if(!o.curlStates.has(e))return!1}else if(o?.curlStates.has(e))return!1}return!0}}const f=p.degToRad(170),N=p.degToRad(150),F={[exports.Finger.THUMB]:[p.degToRad(150),p.degToRad(160),p.degToRad(140)],[exports.Finger.INDEX]:[p.degToRad(155),p.degToRad(40),p.degToRad(175)],[exports.Finger.MIDDLE]:[p.degToRad(155),p.degToRad(40),p.degToRad(130)],[exports.Finger.RING]:[p.degToRad(145),p.degToRad(40),p.degToRad(125)],[exports.Finger.PINKY]:[p.degToRad(150),p.degToRad(35),p.degToRad(145)]};const P={[exports.Finger.THUMB]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.THUMB_CMC,exports.HandPoseLandmark.THUMB_MCP,exports.HandPoseLandmark.THUMB_IP,exports.HandPoseLandmark.THUMB_TIP],[exports.Finger.INDEX]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.INDEX_FINGER_MCP,exports.HandPoseLandmark.INDEX_FINGER_PIP,exports.HandPoseLandmark.INDEX_FINGER_DIP,exports.HandPoseLandmark.INDEX_FINGER_TIP],[exports.Finger.MIDDLE]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.MIDDLE_FINGER_MCP,exports.HandPoseLandmark.MIDDLE_FINGER_PIP,exports.HandPoseLandmark.MIDDLE_FINGER_DIP,exports.HandPoseLandmark.MIDDLE_FINGER_TIP],[exports.Finger.RING]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.RING_FINGER_MCP,exports.HandPoseLandmark.RING_FINGER_PIP,exports.HandPoseLandmark.RING_FINGER_DIP,exports.HandPoseLandmark.RING_FINGER_TIP],[exports.Finger.PINKY]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.PINKY_FINGER_MCP,exports.HandPoseLandmark.PINKY_FINGER_PIP,exports.HandPoseLandmark.PINKY_FINGER_DIP,exports.HandPoseLandmark.PINKY_FINGER_TIP]},d=process.env.PACKAGE_BUILD_TYPE,G={color:"#ffffff",lineWidth:1};const y={color:"#ffffff",radius:3};class H{static getFingerCurlAngles(t,s){const e=L[s],o={};for(let s=0;s<e.length-2;s++){const r=e[s],i=e[s+1],n=e[s+2],x=[r,i,n].join("-"),c=t[r],a=t[i],h=t[n];o[x]=p.getAngleFromPointsIn3D(c,a,h,a)}return o}processedPoints;constructor(t){const s={};for(const e of t)for(const t in L){const o=H.getFingerCurlAngles(e,t);for(const t in o)s[t]||(s[t]=[]),s[t].push(o[t])}const e={};for(const t in s){const o=new p.NumericDataSet(s[t]);e[t]={mean:o.mean,stdDev:o.stddev}}this.processedPoints=e}isMatchedBy(t,s=2){for(const e in L){const o=H.getFingerCurlAngles(t,e);for(const t in o){const{mean:e,stdDev:r}=this.processedPoints[t];if(!p.isInRange(o[t],e-r*s,e+r*s))return!1}}return!0}}const L={[exports.Finger.THUMB]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.THUMB_CMC,exports.HandPoseLandmark.THUMB_MCP,exports.HandPoseLandmark.THUMB_IP,exports.HandPoseLandmark.THUMB_TIP],[exports.Finger.INDEX]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.INDEX_FINGER_MCP,exports.HandPoseLandmark.INDEX_FINGER_PIP,exports.HandPoseLandmark.INDEX_FINGER_DIP,exports.HandPoseLandmark.INDEX_FINGER_TIP],[exports.Finger.MIDDLE]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.MIDDLE_FINGER_MCP,exports.HandPoseLandmark.MIDDLE_FINGER_PIP,exports.HandPoseLandmark.MIDDLE_FINGER_DIP,exports.HandPoseLandmark.MIDDLE_FINGER_TIP],[exports.Finger.RING]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.RING_FINGER_MCP,exports.HandPoseLandmark.RING_FINGER_PIP,exports.HandPoseLandmark.RING_FINGER_DIP,exports.HandPoseLandmark.RING_FINGER_TIP],[exports.Finger.PINKY]:[exports.HandPoseLandmark.WRIST,exports.HandPoseLandmark.PINKY_FINGER_MCP,exports.HandPoseLandmark.PINKY_FINGER_PIP,exports.HandPoseLandmark.PINKY_FINGER_DIP,exports.HandPoseLandmark.PINKY_FINGER_TIP]};exports.BUILD_HASH="4d46b09a99c0294205b422ab416bab0b429e4c70",exports.BUILD_TYPE=d,exports.BaseLandmarkAnalyzer=I,exports.BaseVisionAnalyzer=h,exports.ComplexHandGesture=R,exports.FilesetResolverPath=a,exports.HAND_CONNECTIONS=[[0,1],[1,2],[2,3],[3,4],[0,5],[5,6],[6,7],[7,8],[5,9],[9,10],[10,11],[11,12],[9,13],[13,14],[14,15],[15,16],[13,17],[0,17],[17,18],[18,19],[19,20]],exports.HandGestureSnapshot=H,exports.OnePersonBodyPoseAnalyzer=E,exports.OnePersonFaceMeshAnalyzer=_,exports.OnePersonHandGestureAnalyzer=l,exports.OnePersonHandPoseAnalyzer=T,exports.VERSION="1.3.0",exports.drawConnectors=function(t,s,e,o){const r={...G,...o};for(const o of e){const[e,i]=o,n=s[e],p=s[i];t.beginPath(),t.moveTo(n.x*t.canvas.width,n.y*t.canvas.height),t.lineTo(p.x*t.canvas.width,p.y*t.canvas.height),t.lineWidth=r.lineWidth,t.strokeStyle=r.color,t.stroke()}},exports.drawLandmarks=function(t,s,e){const o={...y,...e};for(const e of s){const{x:s,y:r}=e;t.beginPath(),t.arc(s*t.canvas.width,r*t.canvas.height,o.radius,0,2*Math.PI),t.fillStyle=o.color,t.fill()}};