facecog-liveness-showcase 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.browserslistrc +15 -0
  2. package/.dockerignore +48 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintrc.json +47 -0
  5. package/.vercelignore +7 -0
  6. package/.vscode/extensions.json +5 -0
  7. package/.vscode/settings.json +3 -0
  8. package/DOCKER.md +221 -0
  9. package/Dockerfile +33 -0
  10. package/README.md +268 -0
  11. package/angular.json +156 -0
  12. package/capacitor.config.ts +9 -0
  13. package/docker-compose.dev.yml +20 -0
  14. package/docker-compose.yml +18 -0
  15. package/ionic.config.json +7 -0
  16. package/jest.config.js +38 -0
  17. package/nginx.conf +50 -0
  18. package/package.json +131 -0
  19. package/patches/ng-packagr+20.3.2.patch +60 -0
  20. package/projects/facecog-liveness-verification/README.md +295 -0
  21. package/projects/facecog-liveness-verification/ng-package.json +7 -0
  22. package/projects/facecog-liveness-verification/package.json +48 -0
  23. package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
  24. package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
  25. package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
  26. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
  27. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
  28. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
  29. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
  30. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
  31. package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
  32. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
  33. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
  34. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
  35. package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
  36. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
  37. package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
  38. package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
  39. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
  40. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
  41. package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
  42. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
  43. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
  44. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
  45. package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
  46. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
  47. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
  48. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
  49. package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
  50. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
  51. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
  52. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
  53. package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
  54. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
  55. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
  56. package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
  57. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
  58. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
  59. package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
  60. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
  61. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
  62. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
  63. package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
  64. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
  65. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
  66. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
  67. package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
  68. package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
  69. package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
  70. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
  71. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
  72. package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
  73. package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
  74. package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
  75. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
  76. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
  77. package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
  78. package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
  79. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
  80. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
  81. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
  82. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
  83. package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
  84. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
  85. package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
  86. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
  87. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
  88. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
  89. package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
  90. package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
  91. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
  92. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
  93. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
  94. package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
  95. package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
  96. package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
  97. package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
  98. package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
  99. package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
  100. package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
  101. package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
  102. package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
  103. package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
  104. package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
  105. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
  106. package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
  107. package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
  108. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
  109. package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
  110. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
  111. package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
  112. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
  113. package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
  114. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
  115. package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
  116. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
  117. package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
  118. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
  119. package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
  120. package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
  121. package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
  122. package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
  123. package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
  124. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
  125. package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
  126. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
  127. package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
  128. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
  129. package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
  130. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
  131. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
  132. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
  133. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
  134. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
  135. package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
  136. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
  137. package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
  138. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
  139. package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
  140. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
  141. package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
  142. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
  143. package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
  144. package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
  145. package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
  146. package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
  147. package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
  148. package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
  149. package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
  150. package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
  151. package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
  152. package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
  153. package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
  154. package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
  155. package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
  156. package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
  157. package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
  158. package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
  159. package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
  160. package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
  161. package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
  162. package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
  163. package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
  164. package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
  165. package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
  166. package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
  167. package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
  168. package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
  169. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
  170. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
  171. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
  172. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
  173. package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
  174. package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
  175. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
  176. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
  177. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
  178. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
  179. package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
  180. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
  181. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
  182. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
  183. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
  184. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
  185. package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
  186. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
  187. package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
  188. package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
  189. package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
  190. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
  191. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
  192. package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
  193. package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
  194. package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
  195. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
  196. package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
  197. package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
  198. package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
  199. package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
  200. package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
  201. package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
  202. package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
  203. package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
  204. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
  205. package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
  206. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
  207. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
  208. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
  209. package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
  210. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
  211. package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
  212. package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
  213. package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
  214. package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
  215. package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
  216. package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
  217. package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
  218. package/projects/facecog-liveness-verification-test/src/index.html +31 -0
  219. package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
  220. package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
  221. package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
  222. package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
  223. package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
  224. package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
  225. package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
  226. package/setup-jest.ts +118 -0
  227. package/tsconfig.json +41 -0
  228. package/tsconfig.spec.json +15 -0
  229. package/vercel.json +24 -0
@@ -0,0 +1,287 @@
1
+ import { Injectable } from '@angular/core';
2
+ import * as poseDetection from '@tensorflow-models/pose-detection';
3
+ import * as tf from '@tensorflow/tfjs-core';
4
+ import '@tensorflow/tfjs-backend-webgl';
5
+
6
+ export interface PoseKeypoints {
7
+ keypoints: poseDetection.Keypoint[];
8
+ score: number;
9
+ }
10
+
11
+ @Injectable({
12
+ providedIn: 'root'
13
+ })
14
+ export class OpenposeService {
15
+ private detector: poseDetection.PoseDetector | null = null;
16
+ private isInitialized = false;
17
+
18
+ constructor() {}
19
+
20
+ /**
21
+ * Initialize MoveNet pose detector
22
+ */
23
+ async loadModel(): Promise<void> {
24
+ if (this.isInitialized) {
25
+ console.log('[OpenPose] Model already loaded');
26
+ return;
27
+ }
28
+
29
+ try {
30
+ console.log('[OpenPose] Loading MoveNet model...');
31
+
32
+ // Try WebGL first, fallback to WASM for Safari compatibility
33
+ try {
34
+ await tf.setBackend('webgl');
35
+ await tf.ready();
36
+ console.log('[OpenPose] Using WebGL backend');
37
+ } catch (webglError) {
38
+ console.warn('[OpenPose] WebGL backend failed, trying WASM for Safari compatibility:', webglError);
39
+ await tf.setBackend('wasm');
40
+ await tf.ready();
41
+ console.log('[OpenPose] Using WASM backend (Safari fallback)');
42
+ }
43
+
44
+ // Create MoveNet detector with Lightning model (faster, good for real-time)
45
+ const detectorConfig = {
46
+ modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
47
+ };
48
+
49
+ this.detector = await poseDetection.createDetector(
50
+ poseDetection.SupportedModels.MoveNet,
51
+ detectorConfig
52
+ );
53
+
54
+ this.isInitialized = true;
55
+ console.log('[OpenPose] MoveNet model loaded successfully');
56
+ } catch (error) {
57
+ console.error('[OpenPose] Failed to load model:', error);
58
+ throw new Error('Failed to load pose detection model');
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Detect pose in an image element
64
+ */
65
+ async detectPose(imageElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement): Promise<PoseKeypoints | null> {
66
+ if (!this.isInitialized || !this.detector) {
67
+ console.warn('[OpenPose] Detector not initialized, attempting to load...');
68
+ await this.loadModel();
69
+ }
70
+
71
+ try {
72
+ const poses = await this.detector!.estimatePoses(imageElement);
73
+
74
+ if (poses.length === 0) {
75
+ // Don't log every frame to reduce console spam
76
+ return null;
77
+ }
78
+
79
+ // Return the first detected pose
80
+ const pose = poses[0];
81
+
82
+ // Log keypoint details for debugging
83
+ const rightWrist = pose.keypoints[10]; // Index 10 is right_wrist in MoveNet
84
+ const rightShoulder = pose.keypoints[6]; // Index 6 is right_shoulder
85
+
86
+ console.log('[OpenPose] Pose detected - Score:', pose.score?.toFixed(3),
87
+ 'Right wrist:', rightWrist ? `(${rightWrist.x?.toFixed(3)}, ${rightWrist.y?.toFixed(3)}, score: ${rightWrist.score?.toFixed(3)})` : 'not detected',
88
+ 'Right shoulder:', rightShoulder ? `(${rightShoulder.x?.toFixed(3)}, ${rightShoulder.y?.toFixed(3)}, score: ${rightShoulder.score?.toFixed(3)})` : 'not detected');
89
+
90
+ return {
91
+ keypoints: pose.keypoints,
92
+ score: pose.score || 0
93
+ };
94
+ } catch (error) {
95
+ console.error('[OpenPose] Pose detection error:', error);
96
+ return null;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Detect pose from base64 image string
102
+ */
103
+ async detectPoseFromBase64(base64Image: string): Promise<PoseKeypoints | null> {
104
+ return new Promise((resolve, reject) => {
105
+ const img = new Image();
106
+
107
+ img.onload = async () => {
108
+ try {
109
+ const pose = await this.detectPose(img);
110
+ resolve(pose);
111
+ } catch (error) {
112
+ reject(error);
113
+ }
114
+ };
115
+
116
+ img.onerror = () => {
117
+ reject(new Error('Failed to load image from base64'));
118
+ };
119
+
120
+ img.src = base64Image;
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Get specific keypoint by name
126
+ */
127
+ getKeypoint(pose: PoseKeypoints, keypointName: string): poseDetection.Keypoint | null {
128
+ const keypoint = pose.keypoints.find(kp => kp.name === keypointName);
129
+ return keypoint || null;
130
+ }
131
+
132
+ /**
133
+ * Check if a keypoint is visible (has good confidence)
134
+ */
135
+ isKeypointVisible(keypoint: poseDetection.Keypoint | null, threshold: number = 0.3): boolean {
136
+ if (!keypoint) return false;
137
+ return (keypoint.score || 0) >= threshold;
138
+ }
139
+
140
+ /**
141
+ * Get hand keypoints (wrists)
142
+ */
143
+ getHandKeypoints(pose: PoseKeypoints): {
144
+ leftWrist: poseDetection.Keypoint | null;
145
+ rightWrist: poseDetection.Keypoint | null;
146
+ leftWristVisible: boolean;
147
+ rightWristVisible: boolean;
148
+ } {
149
+ const leftWrist = this.getKeypoint(pose, 'left_wrist');
150
+ const rightWrist = this.getKeypoint(pose, 'right_wrist');
151
+
152
+ return {
153
+ leftWrist,
154
+ rightWrist,
155
+ leftWristVisible: this.isKeypointVisible(leftWrist),
156
+ rightWristVisible: this.isKeypointVisible(rightWrist)
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Get face/head keypoints
162
+ */
163
+ getHeadKeypoints(pose: PoseKeypoints): {
164
+ nose: poseDetection.Keypoint | null;
165
+ leftEye: poseDetection.Keypoint | null;
166
+ rightEye: poseDetection.Keypoint | null;
167
+ leftEar: poseDetection.Keypoint | null;
168
+ rightEar: poseDetection.Keypoint | null;
169
+ } {
170
+ return {
171
+ nose: this.getKeypoint(pose, 'nose'),
172
+ leftEye: this.getKeypoint(pose, 'left_eye'),
173
+ rightEye: this.getKeypoint(pose, 'right_eye'),
174
+ leftEar: this.getKeypoint(pose, 'left_ear'),
175
+ rightEar: this.getKeypoint(pose, 'right_ear')
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Get shoulder keypoints (useful for determining hand positions relative to body)
181
+ */
182
+ getShoulderKeypoints(pose: PoseKeypoints): {
183
+ leftShoulder: poseDetection.Keypoint | null;
184
+ rightShoulder: poseDetection.Keypoint | null;
185
+ } {
186
+ return {
187
+ leftShoulder: this.getKeypoint(pose, 'left_shoulder'),
188
+ rightShoulder: this.getKeypoint(pose, 'right_shoulder')
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Get elbow keypoints (useful for determining arm bend/extension)
194
+ */
195
+ getElbowKeypoints(pose: PoseKeypoints): {
196
+ leftElbow: poseDetection.Keypoint | null;
197
+ rightElbow: poseDetection.Keypoint | null;
198
+ } {
199
+ return {
200
+ leftElbow: this.getKeypoint(pose, 'left_elbow'),
201
+ rightElbow: this.getKeypoint(pose, 'right_elbow')
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Detect thumbs up gesture
207
+ * A thumbs up is detected when:
208
+ * 1. Wrist is raised above shoulder level
209
+ * 2. Wrist is roughly aligned with or above the elbow
210
+ * 3. Hand is visible with good confidence
211
+ */
212
+ detectThumbsUp(pose: PoseKeypoints): {
213
+ leftThumbsUp: boolean;
214
+ rightThumbsUp: boolean;
215
+ anyThumbsUp: boolean;
216
+ confidence: number;
217
+ } {
218
+ const hands = this.getHandKeypoints(pose);
219
+ const shoulders = this.getShoulderKeypoints(pose);
220
+ const elbows = this.getElbowKeypoints(pose);
221
+
222
+ let leftThumbsUp = false;
223
+ let rightThumbsUp = false;
224
+ let maxConfidence = 0;
225
+
226
+ // Check left hand thumbs up
227
+ if (hands.leftWristVisible &&
228
+ shoulders.leftShoulder &&
229
+ elbows.leftElbow &&
230
+ hands.leftWrist) {
231
+
232
+ const wristAboveShoulder = hands.leftWrist.y < shoulders.leftShoulder.y;
233
+ const wristAboveOrNearElbow = hands.leftWrist.y <= elbows.leftElbow.y + 50; // Allow some tolerance
234
+
235
+ // Wrist should be raised (above shoulder)
236
+ if (wristAboveShoulder && wristAboveOrNearElbow) {
237
+ leftThumbsUp = true;
238
+ maxConfidence = Math.max(maxConfidence, hands.leftWrist.score || 0);
239
+ console.log('[OpenPose] Left thumbs up detected!', {
240
+ wrist: hands.leftWrist.y,
241
+ shoulder: shoulders.leftShoulder.y,
242
+ elbow: elbows.leftElbow.y
243
+ });
244
+ }
245
+ }
246
+
247
+ // Check right hand thumbs up
248
+ if (hands.rightWristVisible &&
249
+ shoulders.rightShoulder &&
250
+ elbows.rightElbow &&
251
+ hands.rightWrist) {
252
+
253
+ const wristAboveShoulder = hands.rightWrist.y < shoulders.rightShoulder.y;
254
+ const wristAboveOrNearElbow = hands.rightWrist.y <= elbows.rightElbow.y + 50; // Allow some tolerance
255
+
256
+ // Wrist should be raised (above shoulder)
257
+ if (wristAboveShoulder && wristAboveOrNearElbow) {
258
+ rightThumbsUp = true;
259
+ maxConfidence = Math.max(maxConfidence, hands.rightWrist.score || 0);
260
+ console.log('[OpenPose] Right thumbs up detected!', {
261
+ wrist: hands.rightWrist.y,
262
+ shoulder: shoulders.rightShoulder.y,
263
+ elbow: elbows.rightElbow.y
264
+ });
265
+ }
266
+ }
267
+
268
+ return {
269
+ leftThumbsUp,
270
+ rightThumbsUp,
271
+ anyThumbsUp: leftThumbsUp || rightThumbsUp,
272
+ confidence: maxConfidence
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Cleanup resources
278
+ */
279
+ cleanup(): void {
280
+ if (this.detector) {
281
+ this.detector.dispose();
282
+ this.detector = null;
283
+ this.isInitialized = false;
284
+ console.log('[OpenPose] Detector disposed');
285
+ }
286
+ }
287
+ }
@@ -0,0 +1,353 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { OpenposeService, PoseKeypoints } from './openpose.service';
3
+ import { ReferencePose } from './reference-pose.service';
4
+ import * as poseDetection from '@tensorflow-models/pose-detection';
5
+
6
+ export interface PoseComparisonResult {
7
+ overallScore: number; // 0-1
8
+ handScore: number; // Hand position similarity
9
+ bodyScore: number; // Body pose similarity
10
+ matched: boolean; // True if score > threshold
11
+ feedback: string; // User feedback message
12
+ handsRequired: boolean; // True if reference pose has hand keypoints
13
+ }
14
+
15
+ @Injectable({
16
+ providedIn: 'root'
17
+ })
18
+ export class PoseComparisonService {
19
+ private readonly MATCH_THRESHOLD = 0.75; // 75% similarity required
20
+
21
+ constructor(private openposeService: OpenposeService) {}
22
+
23
+ /**
24
+ * Compare live pose with reference pose
25
+ */
26
+ comparePoses(livePose: PoseKeypoints, referencePose: ReferencePose): PoseComparisonResult {
27
+ try {
28
+ console.log('[Pose Comparison] Comparing poses...');
29
+
30
+ // Check if reference has any hand keypoints (using lower threshold 0.15)
31
+ const refHands = this.getHandKeypointsWithLowerThreshold(referencePose.keypoints);
32
+ const handsRequired = refHands.leftWristVisible || refHands.rightWristVisible;
33
+
34
+ console.log('[Pose Comparison] Reference pose hands required:', handsRequired);
35
+
36
+ // Calculate individual scores
37
+ const handScore = this.calculateHandSimilarity(livePose, referencePose.keypoints);
38
+ const bodyScore = this.calculateBodySimilarity(livePose, referencePose.keypoints);
39
+
40
+ // Weighted overall score (hands are more important for gesture matching)
41
+ // If hands are required but not matched, penalize more heavily
42
+ let overallScore: number;
43
+ if (handsRequired) {
44
+ // Hands are 70% of score when required
45
+ overallScore = (handScore * 0.7) + (bodyScore * 0.3);
46
+ } else {
47
+ // Standard weighting
48
+ overallScore = (handScore * 0.6) + (bodyScore * 0.4);
49
+ }
50
+
51
+ const matched = overallScore >= this.MATCH_THRESHOLD;
52
+
53
+ // Generate feedback
54
+ const feedback = this.generateFeedback(overallScore, handScore, bodyScore, matched, handsRequired);
55
+
56
+ console.log(`[Pose Comparison] Hand: ${(handScore * 100).toFixed(1)}%, Body: ${(bodyScore * 100).toFixed(1)}%, Overall: ${(overallScore * 100).toFixed(1)}%, Matched: ${matched}, HandsRequired: ${handsRequired}`);
57
+
58
+ return {
59
+ overallScore,
60
+ handScore,
61
+ bodyScore,
62
+ matched,
63
+ feedback,
64
+ handsRequired
65
+ };
66
+ } catch (error) {
67
+ console.error('[Pose Comparison] Error comparing poses:', error);
68
+ return {
69
+ overallScore: 0,
70
+ handScore: 0,
71
+ bodyScore: 0,
72
+ matched: false,
73
+ feedback: 'Unable to compare poses',
74
+ handsRequired: false
75
+ };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get hand keypoints with lower threshold (0.15 instead of 0.3)
81
+ */
82
+ private getHandKeypointsWithLowerThreshold(pose: PoseKeypoints): {
83
+ leftWrist: any | null;
84
+ rightWrist: any | null;
85
+ leftWristVisible: boolean;
86
+ rightWristVisible: boolean;
87
+ } {
88
+ const leftWrist = this.openposeService.getKeypoint(pose, 'left_wrist');
89
+ const rightWrist = this.openposeService.getKeypoint(pose, 'right_wrist');
90
+ const lowThreshold = 0.15;
91
+
92
+ return {
93
+ leftWrist,
94
+ rightWrist,
95
+ leftWristVisible: this.openposeService.isKeypointVisible(leftWrist, lowThreshold),
96
+ rightWristVisible: this.openposeService.isKeypointVisible(rightWrist, lowThreshold)
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Calculate hand position similarity
102
+ */
103
+ private calculateHandSimilarity(livePose: PoseKeypoints, referencePose: PoseKeypoints): number {
104
+ // Use lower threshold (0.15) for detecting hands in both live and reference
105
+ const liveHands = this.getHandKeypointsWithLowerThreshold(livePose);
106
+ const refHands = this.getHandKeypointsWithLowerThreshold(referencePose);
107
+ const liveShoulders = this.openposeService.getShoulderKeypoints(livePose);
108
+ const refShoulders = this.openposeService.getShoulderKeypoints(referencePose);
109
+ const liveHead = this.openposeService.getHeadKeypoints(livePose);
110
+ const refHead = this.openposeService.getHeadKeypoints(referencePose);
111
+
112
+ let scores: number[] = [];
113
+ let leftHandScore: number | null = null;
114
+ let rightHandScore: number | null = null;
115
+
116
+ // Compare left hand position (relative to body center)
117
+ if (refHands.leftWristVisible) {
118
+ if (liveHands.leftWristVisible && liveHead.nose && refHead.nose &&
119
+ liveShoulders.leftShoulder && refShoulders.leftShoulder) {
120
+
121
+ const liveLeftHandPos = this.normalizeKeypointPosition(
122
+ liveHands.leftWrist!,
123
+ liveHead.nose,
124
+ liveShoulders.leftShoulder,
125
+ liveShoulders.rightShoulder
126
+ );
127
+
128
+ const refLeftHandPos = this.normalizeKeypointPosition(
129
+ refHands.leftWrist!,
130
+ refHead.nose,
131
+ refShoulders.leftShoulder,
132
+ refShoulders.rightShoulder
133
+ );
134
+
135
+ leftHandScore = this.calculatePositionSimilarity(liveLeftHandPos, refLeftHandPos);
136
+ scores.push(leftHandScore);
137
+ } else {
138
+ // Reference has left hand but live doesn't - penalize heavily
139
+ leftHandScore = 0.0;
140
+ scores.push(leftHandScore);
141
+ }
142
+ }
143
+
144
+ // Compare right hand position (relative to body center)
145
+ if (refHands.rightWristVisible) {
146
+ if (liveHands.rightWristVisible && liveHead.nose && refHead.nose &&
147
+ liveShoulders.rightShoulder && refShoulders.rightShoulder) {
148
+
149
+ const liveRightHandPos = this.normalizeKeypointPosition(
150
+ liveHands.rightWrist!,
151
+ liveHead.nose,
152
+ liveShoulders.leftShoulder,
153
+ liveShoulders.rightShoulder
154
+ );
155
+
156
+ const refRightHandPos = this.normalizeKeypointPosition(
157
+ refHands.rightWrist!,
158
+ refHead.nose,
159
+ refShoulders.leftShoulder,
160
+ refShoulders.rightShoulder
161
+ );
162
+
163
+ rightHandScore = this.calculatePositionSimilarity(liveRightHandPos, refRightHandPos);
164
+ scores.push(rightHandScore);
165
+ } else {
166
+ // Reference has right hand but live doesn't - penalize heavily
167
+ rightHandScore = 0.0;
168
+ scores.push(rightHandScore);
169
+ }
170
+ }
171
+
172
+ // If no hands visible in reference (even with lower 0.15 threshold), return full score
173
+ // This means the reference pose genuinely doesn't require hand matching
174
+ if (!refHands.leftWristVisible && !refHands.rightWristVisible) {
175
+ console.log('[Pose Comparison] No hands detected in reference pose (threshold 0.15) - hand matching not required');
176
+ return 1.0;
177
+ }
178
+
179
+ // If no scores calculated (shouldn't happen), return 0
180
+ if (scores.length === 0) {
181
+ return 0.0;
182
+ }
183
+
184
+ // IMPORTANT: Both hands must match if both are in reference
185
+ // If reference has both hands, BOTH must be visible and match in live pose
186
+ const bothHandsInReference = refHands.leftWristVisible && refHands.rightWristVisible;
187
+ const bothHandsInLive = liveHands.leftWristVisible && liveHands.rightWristVisible;
188
+
189
+ if (bothHandsInReference && !bothHandsInLive) {
190
+ console.log('[Pose Comparison] Reference has BOTH hands, but live pose only has ONE hand - FAIL');
191
+ return 0.0; // Hard fail if reference has both hands but live doesn't
192
+ }
193
+
194
+ // Average hand scores (all required hands must match)
195
+ const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
196
+
197
+ console.log(`[Pose Comparison] Hand scores - Left: ${leftHandScore?.toFixed(2) ?? 'N/A'}, Right: ${rightHandScore?.toFixed(2) ?? 'N/A'}, Avg: ${avgScore.toFixed(2)}`);
198
+
199
+ return avgScore;
200
+ }
201
+
202
+ /**
203
+ * Calculate body pose similarity (shoulders, head alignment)
204
+ */
205
+ private calculateBodySimilarity(livePose: PoseKeypoints, referencePose: PoseKeypoints): number {
206
+ const liveShoulders = this.openposeService.getShoulderKeypoints(livePose);
207
+ const refShoulders = this.openposeService.getShoulderKeypoints(referencePose);
208
+ const liveHead = this.openposeService.getHeadKeypoints(livePose);
209
+ const refHead = this.openposeService.getHeadKeypoints(referencePose);
210
+
211
+ let scores: number[] = [];
212
+
213
+ // Compare shoulder alignment (angle)
214
+ if (liveShoulders.leftShoulder && liveShoulders.rightShoulder &&
215
+ refShoulders.leftShoulder && refShoulders.rightShoulder) {
216
+
217
+ const liveShoulderAngle = this.calculateAngle(
218
+ liveShoulders.leftShoulder,
219
+ liveShoulders.rightShoulder
220
+ );
221
+
222
+ const refShoulderAngle = this.calculateAngle(
223
+ refShoulders.leftShoulder,
224
+ refShoulders.rightShoulder
225
+ );
226
+
227
+ const angleDiff = Math.abs(liveShoulderAngle - refShoulderAngle);
228
+ const angleSim = Math.max(0, 1 - (angleDiff / 45)); // 45 degree tolerance
229
+ scores.push(angleSim);
230
+ }
231
+
232
+ // Compare head position (relative to shoulders)
233
+ if (liveHead.nose && refHead.nose &&
234
+ liveShoulders.leftShoulder && liveShoulders.rightShoulder &&
235
+ refShoulders.leftShoulder && refShoulders.rightShoulder) {
236
+
237
+ const liveHeadPos = this.normalizeKeypointPosition(
238
+ liveHead.nose,
239
+ liveHead.nose,
240
+ liveShoulders.leftShoulder,
241
+ liveShoulders.rightShoulder
242
+ );
243
+
244
+ const refHeadPos = this.normalizeKeypointPosition(
245
+ refHead.nose,
246
+ refHead.nose,
247
+ refShoulders.leftShoulder,
248
+ refShoulders.rightShoulder
249
+ );
250
+
251
+ const headSim = this.calculatePositionSimilarity(liveHeadPos, refHeadPos);
252
+ scores.push(headSim);
253
+ }
254
+
255
+ // If no body keypoints visible, return 0
256
+ if (scores.length === 0) {
257
+ return 0.0;
258
+ }
259
+
260
+ // Average body scores
261
+ return scores.reduce((a, b) => a + b, 0) / scores.length;
262
+ }
263
+
264
+ /**
265
+ * Normalize keypoint position relative to body center and scale
266
+ */
267
+ private normalizeKeypointPosition(
268
+ keypoint: poseDetection.Keypoint,
269
+ referencePoint: poseDetection.Keypoint, // Usually nose/head center
270
+ leftShoulder: poseDetection.Keypoint | null,
271
+ rightShoulder: poseDetection.Keypoint | null
272
+ ): { x: number; y: number } {
273
+ // Calculate body center
274
+ let centerX = referencePoint.x;
275
+ let centerY = referencePoint.y;
276
+
277
+ // Calculate body scale (shoulder width)
278
+ let scale = 1.0;
279
+ if (leftShoulder && rightShoulder) {
280
+ const shoulderWidth = Math.abs(rightShoulder.x - leftShoulder.x);
281
+ scale = shoulderWidth || 1.0;
282
+
283
+ // Update center to midpoint between shoulders
284
+ centerX = (leftShoulder.x + rightShoulder.x) / 2;
285
+ }
286
+
287
+ // Normalize position relative to center and scale
288
+ return {
289
+ x: (keypoint.x - centerX) / scale,
290
+ y: (keypoint.y - centerY) / scale
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Calculate similarity between two normalized positions
296
+ */
297
+ private calculatePositionSimilarity(pos1: { x: number; y: number }, pos2: { x: number; y: number }): number {
298
+ const dx = pos1.x - pos2.x;
299
+ const dy = pos1.y - pos2.y;
300
+ const distance = Math.sqrt(dx * dx + dy * dy);
301
+
302
+ // Distance threshold: 0.3 (30% of body scale)
303
+ const threshold = 0.3;
304
+ const similarity = Math.max(0, 1 - (distance / threshold));
305
+
306
+ return similarity;
307
+ }
308
+
309
+ /**
310
+ * Calculate angle between two points (in degrees)
311
+ */
312
+ private calculateAngle(point1: poseDetection.Keypoint, point2: poseDetection.Keypoint): number {
313
+ const dx = point2.x - point1.x;
314
+ const dy = point2.y - point1.y;
315
+ const angleRad = Math.atan2(dy, dx);
316
+ const angleDeg = (angleRad * 180) / Math.PI;
317
+ return angleDeg;
318
+ }
319
+
320
+ /**
321
+ * Generate user feedback based on scores
322
+ */
323
+ private generateFeedback(
324
+ overallScore: number,
325
+ handScore: number,
326
+ bodyScore: number,
327
+ matched: boolean,
328
+ handsRequired: boolean = false
329
+ ): string {
330
+ if (matched) {
331
+ return `Perfect match! ${Math.round(overallScore * 100)}%`;
332
+ }
333
+
334
+ if (overallScore >= 0.6) {
335
+ return `Almost there! ${Math.round(overallScore * 100)}% - Keep holding the pose`;
336
+ }
337
+
338
+ // If hands are required but not visible/matched
339
+ if (handsRequired && handScore < 0.3) {
340
+ return 'Show your hand in the frame - match the arm position in the reference';
341
+ }
342
+
343
+ if (handScore < 0.5) {
344
+ return 'Adjust your hand/arm position to match the reference';
345
+ }
346
+
347
+ if (bodyScore < 0.5) {
348
+ return 'Adjust your body alignment to match the reference';
349
+ }
350
+
351
+ return `${Math.round(overallScore * 100)}% - Try to match the reference pose`;
352
+ }
353
+ }