UNPKG

@glyph-cat/ml-helpers

Version:

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

1 lines 42.8 kB
{"version":3,"file":"index.mjs","sources":["../../src/constants/public.ts","../../src/analyzers/body-pose/index.ts","../../src/analyzers/hand-pose/index.ts","../../src/analyzers/hand-gesture/index.ts","../../src/complex-hand-gesture/index.ts","../../src/analyzers/base-classes/index.ts","../../src/analyzers/hand-pose/utils.ts","../../src/drawing-utils/draw-connectors/index.ts","../../src/drawing-utils/draw-landmarks/index.ts","../../src/hand-gesture-snapshot/index.ts"],"sourcesContent":["/**\n * - A replacement for `import { HAND_CONNECTIONS } from '@mediapipe/hands'`\n * - \"TypeError: connections is not iterable\" is thrown when trying to use it.\n * - No idea why the f••• it is `undefined`.\n * @public\n */\nexport const 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]] as Array<[start: number, end: number]>\n\n/**\n * @public\n */\nexport enum VisionAnalyzerState {\n CREATED,\n INITIALIZING,\n STANDBY,\n ACTIVE,\n DISPOSED,\n}\n","import { LazyValue, reflect1D } from '@glyph-cat/swiss-army-knife'\nimport { NormalizedLandmark, PoseLandmarker, PoseLandmarkerResult } from '@mediapipe/tasks-vision'\nimport { BaseLandmarkAnalyzer } from '../base-classes'\n\n/**\n * @public\n */\nexport type OnePersonBodyPoseAnalyzerResult = Array<NormalizedLandmark>\n\n/**\n * @public\n */\nexport class OnePersonBodyPoseAnalyzer extends BaseLandmarkAnalyzer<PoseLandmarker, OnePersonBodyPoseAnalyzerResult> {\n\n /**\n * @internal\n */\n private static M$taskRunnerGetter = new LazyValue(async () => {\n return PoseLandmarker.createFromOptions(\n await BaseLandmarkAnalyzer.getVision(),\n {\n baseOptions: {\n modelAssetPath: '/mediapipe/models/pose_landmarker_lite.task',\n delegate: 'GPU',\n },\n numPoses: 1,\n runningMode: 'VIDEO',\n }\n )\n })\n\n constructor(videoElement: HTMLVideoElement) {\n super(videoElement, new Array(Object.keys(BodyPoseLandmark).length / 2).fill({\n x: 0,\n y: 0,\n z: 0,\n visibility: 0,\n }), OnePersonBodyPoseAnalyzer.M$taskRunnerGetter, 'OnePersonBodyPoseAnalyzer')\n }\n\n protected getProcessedResult(rawResult: PoseLandmarkerResult): OnePersonBodyPoseAnalyzerResult {\n const processedLandmarks: OnePersonBodyPoseAnalyzerResult = []\n if (rawResult.landmarks.length <= 0) { return }\n const landmarks = rawResult.landmarks[0]\n for (const landmark of landmarks) {\n processedLandmarks.push({\n ...landmark,\n x: reflect1D(landmark.x, 0.5), // These values range between 0 to 1\n visibility: 1,\n })\n }\n return processedLandmarks\n }\n\n}\n\n/**\n * @public\n */\nexport enum BodyPoseLandmark {\n NOSE,\n LEFT_EYE_INNER,\n LEFT_EYE,\n LEFT_EYE_OUTER,\n RIGHT_EYE_INNER,\n RIGHT_EYE,\n RIGHT_EYE_OUTER,\n LEFT_EAR,\n RIGHT_EAR,\n MOUTH_LEFT,\n MOUTH_RIGHT,\n LEFT_SHOULDER,\n RIGHT_SHOULDER,\n LEFT_ELBOW,\n RIGHT_ELBOW,\n LEFT_WRIST,\n RIGHT_WRIST,\n LEFT_PINKY,\n RIGHT_PINKY,\n LEFT_INDEX,\n RIGHT_INDEX,\n LEFT_THUMB,\n RIGHT_THUMB,\n LEFT_HIP,\n RIGHT_HIP,\n LEFT_KNEE,\n RIGHT_KNEE,\n LEFT_ANKLE,\n RIGHT_ANKLE,\n LEFT_HEEL,\n RIGHT_HEEL,\n LEFT_FOOT_INDEX,\n RIGHT_FOOT_INDEX,\n}\n","import { LazyValue, reflect1D } from '@glyph-cat/swiss-army-knife'\nimport { HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from '@mediapipe/tasks-vision'\nimport { BaseLandmarkAnalyzer } from '../base-classes'\nimport { BodyPoseLandmark, OnePersonBodyPoseAnalyzer } from '../body-pose'\nimport { getHandedness } from './utils'\n\n/**\n * @public\n */\nexport type OnePersonHandPoseAnalyzerHandResult = Array<NormalizedLandmark>\n\n/**\n * @public\n */\nexport interface OnePersonHandPoseAnalyzerResult {\n L?: OnePersonHandPoseAnalyzerHandResult\n R?: OnePersonHandPoseAnalyzerHandResult\n}\n\n/**\n * @public\n */\nexport class OnePersonHandPoseAnalyzer extends BaseLandmarkAnalyzer<HandLandmarker, OnePersonHandPoseAnalyzerResult> {\n\n /**\n * @internal\n */\n private static M$taskRunnerGetter = new LazyValue(async () => {\n return HandLandmarker.createFromOptions(\n await BaseLandmarkAnalyzer.getVision(),\n {\n baseOptions: {\n modelAssetPath: '/mediapipe/models/hand_landmarker.task',\n delegate: 'GPU',\n },\n numHands: 2,\n runningMode: 'VIDEO',\n }\n )\n })\n\n constructor(private readonly bodyPoseAnalyzer: OnePersonBodyPoseAnalyzer) {\n super(\n bodyPoseAnalyzer.videoElement,\n {},\n OnePersonHandPoseAnalyzer.M$taskRunnerGetter,\n 'OnePersonHandPoseAnalyzer',\n )\n }\n\n protected getProcessedResult(rawResult: HandLandmarkerResult): OnePersonHandPoseAnalyzerResult {\n const bodyPoseResult = this.bodyPoseAnalyzer.result.get()\n const processedLandmarks: OnePersonHandPoseAnalyzerResult = {}\n for (const landmarks of rawResult.landmarks) {\n const subLandmark: Array<NormalizedLandmark> = []\n let wrist: NormalizedLandmark\n for (let i = 0; i < landmarks.length; i++) {\n const landmark = landmarks[i]\n const flippedLandmark = {\n ...landmark,\n x: reflect1D(landmark.x, 0.5),\n visibility: 1,\n }\n if (i === HandPoseLandmark.WRIST) {\n wrist = flippedLandmark\n }\n subLandmark.push(flippedLandmark)\n }\n const handedness = getHandedness(\n wrist,\n bodyPoseResult[BodyPoseLandmark.LEFT_WRIST],\n bodyPoseResult[BodyPoseLandmark.RIGHT_WRIST],\n )\n processedLandmarks[handedness] = subLandmark\n }\n return processedLandmarks\n }\n\n}\n\n/**\n * @public\n */\nexport enum HandPoseLandmark {\n WRIST,\n THUMB_CMC,\n THUMB_MCP,\n THUMB_IP,\n THUMB_TIP,\n INDEX_FINGER_MCP,\n INDEX_FINGER_PIP,\n INDEX_FINGER_DIP,\n INDEX_FINGER_TIP,\n MIDDLE_FINGER_MCP,\n MIDDLE_FINGER_PIP,\n MIDDLE_FINGER_DIP,\n MIDDLE_FINGER_TIP,\n RING_FINGER_MCP,\n RING_FINGER_PIP,\n RING_FINGER_DIP,\n RING_FINGER_TIP,\n PINKY_FINGER_MCP,\n PINKY_FINGER_PIP,\n PINKY_FINGER_DIP,\n PINKY_FINGER_TIP,\n}\n","import { LazyValue, reflect1D } from '@glyph-cat/swiss-army-knife'\nimport {\n GestureRecognizer,\n GestureRecognizerResult,\n NormalizedLandmark,\n} from '@mediapipe/tasks-vision'\nimport { BaseVisionAnalyzer } from '../base-classes'\nimport { BodyPoseLandmark, OnePersonBodyPoseAnalyzer } from '../body-pose'\nimport { HandPoseLandmark } from '../hand-pose'\nimport { getHandedness } from '../hand-pose/utils'\n\n/**\n * @public\n */\nexport type OnePersonHandGestureAnalyzerHandResult = [\n gesture: HandGesture,\n landmarks: Array<NormalizedLandmark>,\n]\n\n/**\n * @public\n */\nexport interface OnePersonHandGestureAnalyzerResult {\n L?: OnePersonHandGestureAnalyzerHandResult\n R?: OnePersonHandGestureAnalyzerHandResult\n}\n\n/**\n * @public\n */\nexport class OnePersonHandGestureAnalyzer extends BaseVisionAnalyzer<GestureRecognizer, OnePersonHandGestureAnalyzerResult> {\n\n /**\n * @internal\n */\n private static M$taskRunnerGetter = new LazyValue(async () => {\n return GestureRecognizer.createFromOptions(\n await BaseVisionAnalyzer.getVision(),\n {\n baseOptions: {\n modelAssetPath: '/mediapipe/models/gesture_recognizer.task',\n delegate: 'GPU',\n },\n numHands: 2,\n runningMode: 'VIDEO',\n }\n )\n })\n\n constructor(private readonly bodyPoseAnalyzer: OnePersonBodyPoseAnalyzer) {\n super(\n bodyPoseAnalyzer.videoElement,\n {},\n OnePersonHandGestureAnalyzer.M$taskRunnerGetter,\n 'recognizeForVideo',\n 'OnePersonHandGestureAnalyzer',\n )\n }\n\n protected getProcessedResult(rawResult: GestureRecognizerResult): OnePersonHandGestureAnalyzerResult {\n const bodyPoseResult = this.bodyPoseAnalyzer.result.get()\n const processedResults: OnePersonHandGestureAnalyzerResult = {}\n for (const landmarksIndex in rawResult.landmarks) {\n const landmarks = rawResult.landmarks[landmarksIndex]\n const subLandmark: Array<NormalizedLandmark> = []\n let wrist: NormalizedLandmark\n for (let i = 0; i < landmarks.length; i++) {\n const landmark = landmarks[i]\n const flippedLandmark = {\n ...landmark,\n x: reflect1D(landmark.x, 0.5),\n visibility: 1,\n }\n if (i === HandPoseLandmark.WRIST) {\n wrist = flippedLandmark\n }\n subLandmark.push(flippedLandmark)\n }\n const handedness = getHandedness(\n wrist,\n bodyPoseResult[BodyPoseLandmark.LEFT_WRIST],\n bodyPoseResult[BodyPoseLandmark.RIGHT_WRIST],\n )\n processedResults[handedness] = [\n rawResult.gestures[landmarksIndex][0].categoryName as HandGesture ?? HandGesture.NONE,\n subLandmark,\n ]\n }\n return processedResults\n }\n\n}\n\n/**\n * @public\n */\nexport enum HandGesture {\n NONE = 'None',\n CLOSED_FIST = 'Closed_Fist',\n OPEN_PALM = 'Open_Palm',\n POINTING_UP = 'Pointing_Up',\n THUMB_DOWN = 'Thumb_Down',\n THUMB_UP = 'Thumb_Up',\n VICTORY = 'Victory',\n I_LOVE_YOU = 'ILoveYou',\n}\n","import {\n degToRad,\n fullyEnumerate,\n getAngleFromPointsIn3D,\n hasProperty,\n NumericDataSet,\n PartialRecord,\n} from '@glyph-cat/swiss-army-knife'\nimport { NormalizedLandmark } from '@mediapipe/hands'\nimport { HandPoseLandmark } from '../analyzers'\n\n/**\n * @public\n */\nexport enum Finger {\n THUMB = 'T',\n INDEX = 'I',\n MIDDLE = 'M',\n RING = 'R',\n PINKY = 'P',\n}\nfullyEnumerate(Finger)\n\n/**\n * @public\n */\nexport enum FingerCurl {\n STRAIGHT = 1,\n HALF,\n FULL,\n}\n\n/**\n * @public\n */\nexport type FingerCurlExpression = {\n is: FingerCurl\n isOneOf?: never\n isNot?: never\n isNotOneOf?: never\n} | {\n is?: never\n isOneOf: Array<FingerCurl>\n isNot?: never\n isNotOneOf?: never\n} | {\n is?: never\n isOneOf?: never\n isNot: FingerCurl\n isNotOneOf?: never\n} | {\n is?: never\n isOneOf?: never\n isNot?: never\n isNotOneOf: Array<FingerCurl>\n}\n\ninterface SimplifiedFingerCurlExpression {\n curlStates: Set<FingerCurl>\n is: boolean\n}\n\n/**\n * @public\n */\nexport class ComplexHandGesture {\n\n static getFingerCurlAngles(\n hand: Array<NormalizedLandmark>,\n finger: Finger\n ): Array<number> {\n const fingerConnection = FingerConnections[finger]\n const angles: Array<number> = []\n for (let i = 0; i < (fingerConnection.length - 2); i++) {\n const pointA = hand[fingerConnection[i]]\n const midPoint = hand[fingerConnection[i + 1]]\n const pointB = hand[fingerConnection[i + 2]]\n angles.push(getAngleFromPointsIn3D(pointA, midPoint, pointB, midPoint))\n }\n return angles\n }\n\n static determineFingerCurl(\n hand: Array<NormalizedLandmark>,\n finger: Finger\n ): FingerCurl {\n const data = new NumericDataSet(this.getFingerCurlAngles(hand, finger))\n if (finger !== Finger.THUMB && data.mean >= DEFAULT_STRAIGHT_THRESHOLD) {\n return FingerCurl.STRAIGHT\n } else if (data.mean >= THUMB_STRAIGHT_THRESHOLD) {\n return FingerCurl.STRAIGHT\n }\n return diffDoesNotExceedDelta(data.values, FULL_CURL_ANGLE_LOOKUP[finger], 10)\n ? FingerCurl.FULL\n : FingerCurl.HALF\n }\n\n /**\n * @internal\n */\n private readonly M$compactFingerCurlExpression: PartialRecord<Finger, Readonly<SimplifiedFingerCurlExpression>>\n\n constructor(fingerCurlStates: PartialRecord<Finger, FingerCurlExpression>) {\n const compactFingerCurlExpression: PartialRecord<Finger, Readonly<SimplifiedFingerCurlExpression>> = {}\n for (const curlState in fingerCurlStates) {\n const curlDefinition = fingerCurlStates[curlState as Finger]\n if (hasProperty(curlDefinition, 'is')) {\n compactFingerCurlExpression[curlState as Finger] = {\n curlStates: new Set([curlDefinition.is]),\n is: true,\n }\n } else if (hasProperty(curlDefinition, 'isOneOf')) {\n compactFingerCurlExpression[curlState as Finger] = {\n curlStates: new Set([...curlDefinition.isOneOf]),\n is: true,\n }\n } else if (hasProperty(curlDefinition, 'isNot')) {\n compactFingerCurlExpression[curlState as Finger] = {\n curlStates: new Set([curlDefinition.isNot]),\n is: false,\n }\n } else if (hasProperty(curlDefinition, 'isNotOneOf')) {\n compactFingerCurlExpression[curlState as Finger] = {\n curlStates: new Set([...curlDefinition.isNotOneOf]),\n is: false,\n }\n }\n }\n this.M$compactFingerCurlExpression = compactFingerCurlExpression\n }\n\n isMatchedBy(hand: Array<NormalizedLandmark>): boolean {\n for (const curlState in this.M$compactFingerCurlExpression) {\n if (!hasProperty(this.M$compactFingerCurlExpression, curlState)) { continue }\n const fingerCurlResult = ComplexHandGesture.determineFingerCurl(hand, curlState as Finger)\n const curlDefinition = this.M$compactFingerCurlExpression[curlState as Finger]\n if (curlDefinition.is) {\n if (!curlDefinition.curlStates.has(fingerCurlResult)) {\n return false\n }\n } else {\n if (curlDefinition.curlStates.has(fingerCurlResult)) {\n return false\n }\n }\n }\n return true\n }\n\n}\n\nconst DEFAULT_STRAIGHT_THRESHOLD = degToRad(170)\nconst THUMB_STRAIGHT_THRESHOLD = degToRad(150)\n\n// TODO: We can further refine this based on pitch/roll/yaw of the hand\n// the lookup table below is just for front-facing gestures\nconst FULL_CURL_ANGLE_LOOKUP: Record<Finger, [number, number, number]> = {\n [Finger.THUMB]: [degToRad(150), degToRad(160), degToRad(140)],\n [Finger.INDEX]: [degToRad(155), degToRad(40), degToRad(175)],\n [Finger.MIDDLE]: [degToRad(155), degToRad(40), degToRad(130)],\n [Finger.RING]: [degToRad(145), degToRad(40), degToRad(125)],\n [Finger.PINKY]: [degToRad(150), degToRad(35), degToRad(145)],\n}\n\n// TODO: better name\nfunction diffDoesNotExceedDelta(\n a: Array<number> | ReadonlyArray<number>,\n b: Array<number> | ReadonlyArray<number>,\n delta: number\n): boolean {\n for (let i = 0; i < a.length; i++) {\n if (Math.abs(a[i] - b[i]) > delta) {\n return false\n }\n }\n return true\n}\n\n// TOFIX: may be we should not consider angle at DIP\n// Only consider angles at PIP and MCP, if both fulfill then consider full curl, if only one then half curl\n\nconst FingerConnections = {\n [Finger.THUMB]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.THUMB_CMC,\n HandPoseLandmark.THUMB_MCP,\n HandPoseLandmark.THUMB_IP,\n HandPoseLandmark.THUMB_TIP,\n ],\n [Finger.INDEX]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.INDEX_FINGER_MCP,\n HandPoseLandmark.INDEX_FINGER_PIP,\n HandPoseLandmark.INDEX_FINGER_DIP,\n HandPoseLandmark.INDEX_FINGER_TIP,\n ],\n [Finger.MIDDLE]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.MIDDLE_FINGER_MCP,\n HandPoseLandmark.MIDDLE_FINGER_PIP,\n HandPoseLandmark.MIDDLE_FINGER_DIP,\n HandPoseLandmark.MIDDLE_FINGER_TIP,\n ],\n [Finger.RING]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.RING_FINGER_MCP,\n HandPoseLandmark.RING_FINGER_PIP,\n HandPoseLandmark.RING_FINGER_DIP,\n HandPoseLandmark.RING_FINGER_TIP,\n ],\n [Finger.PINKY]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.PINKY_FINGER_MCP,\n HandPoseLandmark.PINKY_FINGER_PIP,\n HandPoseLandmark.PINKY_FINGER_DIP,\n HandPoseLandmark.PINKY_FINGER_TIP,\n ],\n} as const\n","import {\n Awaitable,\n createEnumToStringConverter,\n LazyValue,\n NotImplementedError,\n StringRecord,\n} from '@glyph-cat/swiss-army-knife'\nimport { FilesetResolver } from '@mediapipe/tasks-vision'\nimport { SimpleFiniteStateManager, SimpleStateManager } from 'cotton-box'\nimport { VisionLandmarker, WasmFileset } from '../../abstractions'\nimport { VisionAnalyzerState } from '../../constants'\n\n/**\n * @public\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class BaseVisionAnalyzer<TaskRunner extends StringRecord<any>, Result> {\n\n private static _vision: Promise<WasmFileset>\n\n static async getVision(): Promise<WasmFileset> {\n if (!this._vision) {\n this._vision = FilesetResolver.forVisionTasks(\n // 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm'\n '/mediapipe/wasm'\n )\n }\n return this._vision\n }\n\n protected taskRunner: TaskRunner\n protected lastRequestedAnimationFrame: number\n readonly result: SimpleStateManager<Result>\n readonly state: SimpleFiniteStateManager<VisionAnalyzerState>\n\n constructor(\n readonly videoElement: HTMLVideoElement,\n initialResult: Result,\n taskRunnerGetter: LazyValue<Awaitable<TaskRunner>>,\n readonly detectionMethodName: 'detectForVideo' | 'recognizeForVideo',\n displayName: string,\n ) {\n this.start = this.start.bind(this)\n this.stop = this.stop.bind(this)\n this.getProcessedResult = this.getProcessedResult.bind(this)\n this.dispose = this.dispose.bind(this)\n\n // NOTE: `initialResult` was originally an extendable/inheritable property\n // https://stackoverflow.com/a/43595944/5810737\n this.result = new SimpleStateManager<Result>(initialResult)\n\n this.state = new SimpleFiniteStateManager(VisionAnalyzerState.CREATED, [\n [VisionAnalyzerState.CREATED, VisionAnalyzerState.INITIALIZING],\n [VisionAnalyzerState.CREATED, VisionAnalyzerState.DISPOSED],\n [VisionAnalyzerState.INITIALIZING, VisionAnalyzerState.STANDBY],\n [VisionAnalyzerState.ACTIVE, VisionAnalyzerState.STANDBY],\n [VisionAnalyzerState.STANDBY, VisionAnalyzerState.ACTIVE],\n [VisionAnalyzerState.STANDBY, VisionAnalyzerState.DISPOSED],\n ], {\n // TODO: change `detectionMethodName` in constructor to visionAnalyzerName, then from there decide whether to call 'detectForVideo' or 'recognizeForVideo'; so that we can use the visionAnalyzerName for the state as well.\n name: 'VisionAnalyzer',\n serializeState: createEnumToStringConverter(VisionAnalyzerState),\n })\n\n const asyncCb = async () => {\n this.state.set(VisionAnalyzerState.INITIALIZING)\n this.taskRunner = await taskRunnerGetter.value\n // In case state changed (for example, to disposed, halfway), then keep that state.\n this.state.set((s) => s === VisionAnalyzerState.INITIALIZING ? VisionAnalyzerState.STANDBY : s)\n }; asyncCb()\n }\n\n async start(): Promise<void> {\n if (this.state.get() < VisionAnalyzerState.STANDBY) {\n await this.state.wait((s) => s > VisionAnalyzerState.STANDBY)\n } else if (this.state.get() !== VisionAnalyzerState.STANDBY) {\n return // Early exit\n }\n this.state.set(VisionAnalyzerState.ACTIVE)\n this.lastRequestedAnimationFrame = requestAnimationFrame(this.performAnalysis)\n }\n\n async stop(): Promise<void> {\n await this.state.wait((s) => s === VisionAnalyzerState.CREATED || s > VisionAnalyzerState.STANDBY)\n if (this.state.get() === VisionAnalyzerState.DISPOSED) { return } // Early exit\n cancelAnimationFrame(this.lastRequestedAnimationFrame)\n this.state.set(VisionAnalyzerState.STANDBY)\n }\n\n async dispose(): Promise<void> {\n // NOTE: `taskRunner.close()` is not called and is meant to be kept until\n // the app closes. Calling `taskRunner.close()` on a class lifecycle basis\n // causes a lot of issues:\n // - it seems like there is memory leakage even when `.close()` is called\n // - even if there is no memory leakage, having to fetch and close repeatedly\n // make the app very slow\n // - This is caught from within React's StrictMode + rapidly repeated soft reloads\n // - This can be a problem when the user intentionally stops and resumes\n // the session multiple times\n await this.state.wait((s) => s !== VisionAnalyzerState.INITIALIZING)\n this.result.dispose()\n this.state.set(VisionAnalyzerState.DISPOSED)\n this.state.dispose()\n }\n\n private performAnalysis = async (): Promise<void> => {\n if (this.state.get() !== VisionAnalyzerState.ACTIVE) { return } // Early exit\n const result = this.taskRunner[this.detectionMethodName](this.videoElement, performance.now())\n const processedResult = this.getProcessedResult(result as ReturnType<TaskRunner[typeof this.detectionMethodName]>)\n if (processedResult) { this.result.set(processedResult) }\n this.lastRequestedAnimationFrame = requestAnimationFrame(this.performAnalysis)\n }\n\n protected getProcessedResult(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n rawResult: ReturnType<TaskRunner[typeof this.detectionMethodName]>\n ): Result {\n throw new NotImplementedError()\n }\n\n}\n\n/**\n * @public\n */\nexport class BaseLandmarkAnalyzer<Landmarker extends VisionLandmarker, Result> extends BaseVisionAnalyzer<Landmarker, Result> {\n\n constructor(\n videoElement: HTMLVideoElement,\n initialResult: Result,\n taskRunnerGetter: LazyValue<Awaitable<Landmarker>>,\n displayName: string,\n ) {\n super(\n videoElement,\n initialResult,\n taskRunnerGetter,\n 'detectForVideo',\n displayName,\n )\n }\n\n}\n","import { getDistance2DByCoordinates, Value3D } from '@glyph-cat/swiss-army-knife'\nimport type { OnePersonHandPoseAnalyzerResult } from '.'\n\n/**\n * @public\n */\nexport function getHandedness(\n currentWrist: Value3D,\n leftWrist: Value3D,\n rightWrist: Value3D,\n): keyof OnePersonHandPoseAnalyzerResult {\n // z-position of hand landmark can be up to `4`, which screws up the distance calculation\n // since we only care about what's being translated on screen, x and y should be enough.\n const leftWristDelta = getDistance2DByCoordinates(currentWrist, leftWrist)\n const rightWristDelta = getDistance2DByCoordinates(currentWrist, rightWrist)\n return leftWristDelta < rightWristDelta ? 'L' : 'R'\n}\n","import { Value2D } from '@glyph-cat/swiss-army-knife'\n\n/**\n * @public\n */\nexport interface DrawConnectorOptions {\n /**\n * @defaultValue `'#ffffff'`\n */\n color?: string\n /**\n * @defaultValue `1`\n */\n lineWidth?: number\n}\n\nconst defaultDrawConnectorOptions: Readonly<Required<DrawConnectorOptions>> = {\n color: '#ffffff',\n lineWidth: 1,\n}\n\n/**\n * @public\n */\nexport function drawConnectors(\n ctx: CanvasRenderingContext2D,\n landmarks: Array<Value2D>,\n connections: Array<[start: number, end: number]>,\n style?: DrawConnectorOptions\n): void {\n const mergedStyle = { ...defaultDrawConnectorOptions, ...style }\n for (const connection of connections) {\n const [landmarkKeyA, landmarkKeyB] = connection\n const pointA = landmarks[landmarkKeyA]\n const pointB = landmarks[landmarkKeyB]\n ctx.beginPath()\n ctx.moveTo(pointA.x * ctx.canvas.width, pointA.y * ctx.canvas.height)\n ctx.lineTo(pointB.x * ctx.canvas.width, pointB.y * ctx.canvas.height)\n ctx.lineWidth = mergedStyle.lineWidth\n ctx.strokeStyle = mergedStyle.color\n ctx.stroke()\n }\n}\n","import { Value2D } from '@glyph-cat/swiss-army-knife'\n\n/**\n * @public\n */\nexport interface DrawLandmarkOptions {\n /**\n * @defaultValue `'#ffffff'`\n */\n color?: string\n /**\n * @defaultValue `3`\n */\n radius?: number\n}\n\nconst defaultDrawLandmarkOptions: Readonly<Required<DrawLandmarkOptions>> = {\n color: '#ffffff',\n radius: 3,\n}\n\n/**\n * @public\n */\nexport function drawLandmarks(\n ctx: CanvasRenderingContext2D,\n landmarks: Array<Value2D>,\n style?: DrawLandmarkOptions\n): void {\n const mergedStyle = { ...defaultDrawLandmarkOptions, ...style }\n for (const landmark of landmarks) {\n const { x, y } = landmark\n ctx.beginPath()\n ctx.arc(x * ctx.canvas.width, y * ctx.canvas.height, style.radius, 0, 2 * Math.PI)\n ctx.fillStyle = mergedStyle.color\n ctx.fill()\n }\n}\n","import {\n getAngleFromPointsIn3D,\n isInRange,\n NumericDataSet,\n StringRecord,\n} from '@glyph-cat/swiss-army-knife'\nimport { NormalizedLandmark } from '@mediapipe/hands'\nimport { HandPoseLandmark } from '../analyzers'\nimport { Finger } from '../complex-hand-gesture'\n\n// KIV: Still experimental\n\ninterface ProcessedDataPoint {\n mean: number\n stdDev: number\n}\n\n/**\n * @internal\n */\nexport class HandGestureSnapshot {\n\n static getFingerCurlAngles(\n hand: Array<NormalizedLandmark>,\n finger: Finger\n ): StringRecord<number> {\n const fingerConnection = FingerConnections[finger]\n const angles: StringRecord<number> = {}\n for (let i = 0; i < (fingerConnection.length - 2); i++) {\n const connectionA = fingerConnection[i]\n const connectionMidpoint = fingerConnection[i + 1]\n const connectionB = fingerConnection[i + 2]\n const jointConnectionKey = [connectionA, connectionMidpoint, connectionB].join('-')\n const pointA = hand[connectionA]\n const midPoint = hand[connectionMidpoint]\n const pointB = hand[connectionB]\n angles[jointConnectionKey] = getAngleFromPointsIn3D(pointA, midPoint, pointB, midPoint)\n }\n return angles\n }\n\n private readonly processedPoints: StringRecord<ProcessedDataPoint>\n\n constructor(landmarkSnapshots: Array<Array<NormalizedLandmark>>) {\n const halfProcessedPoints: StringRecord<Array<number>> = {}\n for (const landmarkSnapshot of landmarkSnapshots) {\n for (const finger in FingerConnections) {\n const angles = HandGestureSnapshot.getFingerCurlAngles(landmarkSnapshot, finger as Finger)\n for (const jointConnectionKey in angles) {\n if (!halfProcessedPoints[jointConnectionKey]) {\n halfProcessedPoints[jointConnectionKey] = []\n }\n halfProcessedPoints[jointConnectionKey].push(angles[jointConnectionKey])\n }\n }\n }\n const processedPoints: StringRecord<ProcessedDataPoint> = {}\n for (const jointConnectionKey in halfProcessedPoints) {\n const numericDataSet = new NumericDataSet(halfProcessedPoints[jointConnectionKey])\n processedPoints[jointConnectionKey] = {\n mean: numericDataSet.mean,\n stdDev: numericDataSet.stddev,\n }\n }\n this.processedPoints = processedPoints\n }\n\n isMatchedBy(hand: Array<NormalizedLandmark>, sigma: number = 2): boolean {\n for (const finger in FingerConnections) {\n const angles = HandGestureSnapshot.getFingerCurlAngles(hand, finger as Finger)\n for (const jointConnectionKey in angles) {\n const { mean, stdDev } = this.processedPoints[jointConnectionKey]\n // temp\n // console.log(`stdDev for ${jointConnectionKey}`, stdDev)\n // console.log('='.repeat(20))\n // console.log(`angle for ${jointConnectionKey}`, angles[jointConnectionKey])\n // console.log([mean - (stdDev * sigma), mean, mean + (stdDev * sigma)].join(' <-> '))\n // console.log('is in range', isInRange(\n // angles[jointConnectionKey],\n // mean - (stdDev * sigma),\n // mean + (stdDev * sigma),\n // ))\n if (!isInRange(\n angles[jointConnectionKey],\n mean - (stdDev * sigma),\n mean + (stdDev * sigma),\n )) { return false }\n }\n }\n return true\n }\n\n}\n\nconst FingerConnections = {\n [Finger.THUMB]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.THUMB_CMC,\n HandPoseLandmark.THUMB_MCP,\n HandPoseLandmark.THUMB_IP,\n HandPoseLandmark.THUMB_TIP,\n ],\n [Finger.INDEX]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.INDEX_FINGER_MCP,\n HandPoseLandmark.INDEX_FINGER_PIP,\n HandPoseLandmark.INDEX_FINGER_DIP,\n HandPoseLandmark.INDEX_FINGER_TIP,\n ],\n [Finger.MIDDLE]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.MIDDLE_FINGER_MCP,\n HandPoseLandmark.MIDDLE_FINGER_PIP,\n HandPoseLandmark.MIDDLE_FINGER_DIP,\n HandPoseLandmark.MIDDLE_FINGER_TIP,\n ],\n [Finger.RING]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.RING_FINGER_MCP,\n HandPoseLandmark.RING_FINGER_PIP,\n HandPoseLandmark.RING_FINGER_DIP,\n HandPoseLandmark.RING_FINGER_TIP,\n ],\n [Finger.PINKY]: [\n HandPoseLandmark.WRIST,\n HandPoseLandmark.PINKY_FINGER_MCP,\n HandPoseLandmark.PINKY_FINGER_PIP,\n HandPoseLandmark.PINKY_FINGER_DIP,\n HandPoseLandmark.PINKY_FINGER_TIP,\n ],\n} as const\n"],"names":["HAND_CONNECTIONS","VisionAnalyzerState","BodyPoseLandmark","HandPoseLandmark","HandGesture","Finger","FingerCurl","BaseVisionAnalyzer","videoElement","detectionMethodName","static","getVision","this","_vision","FilesetResolver","forVisionTasks","taskRunner","lastRequestedAnimationFrame","result","state","constructor","initialResult","taskRunnerGetter","displayName","start","bind","stop","getProcessedResult","dispose","SimpleStateManager","SimpleFiniteStateManager","CREATED","INITIALIZING","DISPOSED","STANDBY","ACTIVE","name","serializeState","createEnumToStringConverter","async","set","value","s","asyncCb","get","wait","requestAnimationFrame","performAnalysis","cancelAnimationFrame","performance","now","processedResult","rawResult","NotImplementedError","BaseLandmarkAnalyzer","super","OnePersonBodyPoseAnalyzer","LazyValue","PoseLandmarker","createFromOptions","baseOptions","modelAssetPath","delegate","numPoses","runningMode","Array","Object","keys","length","fill","x","y","z","visibility","M$taskRunnerGetter","processedLandmarks","landmarks","landmark","push","reflect1D","getHandedness","currentWrist","leftWrist","rightWrist","getDistance2DByCoordinates","OnePersonHandPoseAnalyzer","bodyPoseAnalyzer","HandLandmarker","numHands","bodyPoseResult","subLandmark","wrist","i","flippedLandmark","WRIST","LEFT_WRIST","RIGHT_WRIST","OnePersonHandGestureAnalyzer","GestureRecognizer","processedResults","landmarksIndex","gestures","categoryName","NONE","fullyEnumerate","ComplexHandGesture","getFingerCurlAngles","hand","finger","fingerConnection","FingerConnections","angles","pointA","midPoint","pointB","getAngleFromPointsIn3D","determineFingerCurl","data","NumericDataSet","THUMB","mean","DEFAULT_STRAIGHT_THRESHOLD","THUMB_STRAIGHT_THRESHOLD","STRAIGHT","a","b","delta","Math","abs","diffDoesNotExceedDelta","values","FULL_CURL_ANGLE_LOOKUP","FULL","HALF","M$compactFingerCurlExpression","fingerCurlStates","compactFingerCurlExpression","curlState","curlDefinition","hasProperty","curlStates","Set","is","isOneOf","isNot","isNotOneOf","isMatchedBy","fingerCurlResult","has","degToRad","INDEX","MIDDLE","RING","PINKY","THUMB_CMC","THUMB_MCP","THUMB_IP","THUMB_TIP","INDEX_FINGER_MCP","INDEX_FINGER_PIP","INDEX_FINGER_DIP","INDEX_FINGER_TIP","MIDDLE_FINGER_MCP","MIDDLE_FINGER_PIP","MIDDLE_FINGER_DIP","MIDDLE_FINGER_TIP","RING_FINGER_MCP","RING_FINGER_PIP","RING_FINGER_DIP","RING_FINGER_TIP","PINKY_FINGER_MCP","PINKY_FINGER_PIP","PINKY_FINGER_DIP","PINKY_FINGER_TIP","defaultDrawConnectorOptions","color","lineWidth","drawConnectors","ctx","connections","style","mergedStyle","connection","landmarkKeyA","landmarkKeyB","beginPath","moveTo","canvas","width","height","lineTo","strokeStyle","stroke","defaultDrawLandmarkOptions","radius","drawLandmarks","arc","PI","fillStyle","HandGestureSnapshot","connectionA","connectionMidpoint","connectionB","jointConnectionKey","join","processedPoints","landmarkSnapshots","halfProcessedPoints","landmarkSnapshot","numericDataSet","stdDev","stddev","sigma","isInRange"],"mappings":"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"}