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,271 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { OpenposeService, PoseKeypoints } from './openpose.service';
3
+
4
+ export interface ReferencePose {
5
+ keypoints: PoseKeypoints;
6
+ imageData: string; // base64 image
7
+ timestamp: number;
8
+ poseDescription: string;
9
+ }
10
+
11
+ @Injectable({
12
+ providedIn: 'root'
13
+ })
14
+ export class ReferencePoseService {
15
+ private referencePose: ReferencePose | null = null;
16
+
17
+ constructor(private openposeService: OpenposeService) {}
18
+
19
+ /**
20
+ * Extract pose from uploaded reference image
21
+ */
22
+ async extractPoseFromImage(imageFile: File): Promise<ReferencePose> {
23
+ try {
24
+ console.log('[Reference Pose] Extracting pose from uploaded image...');
25
+
26
+ // Convert file to base64
27
+ const base64Image = await this.fileToBase64(imageFile);
28
+
29
+ // Detect pose in the image
30
+ const poseKeypoints = await this.openposeService.detectPoseFromBase64(base64Image);
31
+
32
+ if (!poseKeypoints) {
33
+ throw new Error('No pose detected in the uploaded image');
34
+ }
35
+
36
+ // Validate that the pose has visible key points
37
+ const validation = this.validateReferencePose(poseKeypoints);
38
+ if (!validation.isValid) {
39
+ throw new Error(validation.message);
40
+ }
41
+
42
+ // Create reference pose object
43
+ const referencePose: ReferencePose = {
44
+ keypoints: poseKeypoints,
45
+ imageData: base64Image,
46
+ timestamp: Date.now(),
47
+ poseDescription: this.generatePoseDescription(poseKeypoints)
48
+ };
49
+
50
+ this.referencePose = referencePose;
51
+ console.log('[Reference Pose] Pose extracted successfully:', referencePose.poseDescription);
52
+
53
+ return referencePose;
54
+ } catch (error: any) {
55
+ console.error('[Reference Pose] Failed to extract pose:', error);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Extract pose from base64 image string
62
+ */
63
+ async extractPoseFromBase64(base64Image: string): Promise<ReferencePose> {
64
+ try {
65
+ console.log('[Reference Pose] Extracting pose from base64 image...');
66
+
67
+ // Detect pose in the image
68
+ const poseKeypoints = await this.openposeService.detectPoseFromBase64(base64Image);
69
+
70
+ if (!poseKeypoints) {
71
+ throw new Error('No pose detected in the image');
72
+ }
73
+
74
+ // Validate that the pose has visible key points
75
+ const validation = this.validateReferencePose(poseKeypoints);
76
+ if (!validation.isValid) {
77
+ throw new Error(validation.message);
78
+ }
79
+
80
+ // Create reference pose object
81
+ const referencePose: ReferencePose = {
82
+ keypoints: poseKeypoints,
83
+ imageData: base64Image,
84
+ timestamp: Date.now(),
85
+ poseDescription: this.generatePoseDescription(poseKeypoints)
86
+ };
87
+
88
+ this.referencePose = referencePose;
89
+ console.log('[Reference Pose] Pose extracted successfully:', referencePose.poseDescription);
90
+
91
+ return referencePose;
92
+ } catch (error: any) {
93
+ console.error('[Reference Pose] Failed to extract pose:', error);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Validate reference pose has required keypoints
100
+ */
101
+ private validateReferencePose(pose: PoseKeypoints): { isValid: boolean; message: string } {
102
+ // Check overall pose score (lowered threshold for more lenient validation)
103
+ if (pose.score < 0.15) {
104
+ return {
105
+ isValid: false,
106
+ message: 'Pose detection confidence is too low. Please upload a clearer image showing your full upper body.'
107
+ };
108
+ }
109
+
110
+ // Check if head keypoints are visible (with lower threshold)
111
+ const head = this.openposeService.getHeadKeypoints(pose);
112
+ const headVisible = this.openposeService.isKeypointVisible(head.nose, 0.15) ||
113
+ (this.openposeService.isKeypointVisible(head.leftEye, 0.15) &&
114
+ this.openposeService.isKeypointVisible(head.rightEye, 0.15));
115
+
116
+ if (!headVisible) {
117
+ return {
118
+ isValid: false,
119
+ message: 'Face not clearly visible in the image. Please ensure your face is visible.'
120
+ };
121
+ }
122
+
123
+ // Check if at least one shoulder OR hand is visible (more lenient - allows hand gestures)
124
+ const shoulders = this.openposeService.getShoulderKeypoints(pose);
125
+ // Use lower threshold (0.15) for hand detection
126
+ const leftWrist = this.openposeService.getKeypoint(pose, 'left_wrist');
127
+ const rightWrist = this.openposeService.getKeypoint(pose, 'right_wrist');
128
+ const handsVisible = this.openposeService.isKeypointVisible(leftWrist, 0.15) ||
129
+ this.openposeService.isKeypointVisible(rightWrist, 0.15);
130
+
131
+ const shouldersVisible = this.openposeService.isKeypointVisible(shoulders.leftShoulder, 0.15) ||
132
+ this.openposeService.isKeypointVisible(shoulders.rightShoulder, 0.15);
133
+
134
+ // Log detected keypoints for debugging
135
+ console.log('[Reference Pose] Keypoint visibility:', {
136
+ headVisible,
137
+ shouldersVisible,
138
+ handsVisible,
139
+ leftWristScore: leftWrist?.score?.toFixed(3),
140
+ rightWristScore: rightWrist?.score?.toFixed(3),
141
+ leftShoulderScore: shoulders.leftShoulder?.score?.toFixed(3),
142
+ rightShoulderScore: shoulders.rightShoulder?.score?.toFixed(3)
143
+ });
144
+
145
+ // Accept pose if either shoulders OR hands are visible (allows close-up hand gestures)
146
+ if (!shouldersVisible && !handsVisible) {
147
+ return {
148
+ isValid: false,
149
+ message: 'Upper body not clearly visible. Please upload a photo showing your shoulders and arms. Note: MoveNet detects body positions, not hand gestures.'
150
+ };
151
+ }
152
+
153
+ return {
154
+ isValid: true,
155
+ message: handsVisible ? 'Valid pose detected with arms visible' : 'Valid pose detected (shoulders only)'
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Generate human-readable pose description
161
+ */
162
+ private generatePoseDescription(pose: PoseKeypoints): string {
163
+ const head = this.openposeService.getHeadKeypoints(pose);
164
+ const shoulders = this.openposeService.getShoulderKeypoints(pose);
165
+ const elbows = this.openposeService.getElbowKeypoints(pose);
166
+
167
+ // Use lower threshold for wrist detection
168
+ const leftWrist = this.openposeService.getKeypoint(pose, 'left_wrist');
169
+ const rightWrist = this.openposeService.getKeypoint(pose, 'right_wrist');
170
+ const leftWristVisible = this.openposeService.isKeypointVisible(leftWrist, 0.15);
171
+ const rightWristVisible = this.openposeService.isKeypointVisible(rightWrist, 0.15);
172
+
173
+ const descriptions: string[] = [];
174
+
175
+ // Check head visibility
176
+ if (this.openposeService.isKeypointVisible(head.nose, 0.15)) {
177
+ descriptions.push('Face visible');
178
+ }
179
+
180
+ // Check hand positions with more detail
181
+ if (leftWristVisible || rightWristVisible) {
182
+ const handPositions = this.analyzeHandPositionsDetailed(pose, leftWrist, rightWrist, leftWristVisible, rightWristVisible);
183
+ if (handPositions) {
184
+ descriptions.push(handPositions);
185
+ }
186
+ } else {
187
+ // Check if shoulders/elbows are visible even without wrists
188
+ const leftShoulderVisible = this.openposeService.isKeypointVisible(shoulders.leftShoulder, 0.15);
189
+ const rightShoulderVisible = this.openposeService.isKeypointVisible(shoulders.rightShoulder, 0.15);
190
+
191
+ if (leftShoulderVisible || rightShoulderVisible) {
192
+ descriptions.push('Upper body visible');
193
+ }
194
+ }
195
+
196
+ return descriptions.length > 0 ? descriptions.join(', ') : 'Pose detected';
197
+ }
198
+
199
+ /**
200
+ * Analyze hand positions with more detail
201
+ */
202
+ private analyzeHandPositionsDetailed(
203
+ pose: PoseKeypoints,
204
+ leftWrist: any,
205
+ rightWrist: any,
206
+ leftWristVisible: boolean,
207
+ rightWristVisible: boolean
208
+ ): string {
209
+ const shoulders = this.openposeService.getShoulderKeypoints(pose);
210
+ const elbows = this.openposeService.getElbowKeypoints(pose);
211
+ const positions: string[] = [];
212
+
213
+ // Check left hand position
214
+ if (leftWristVisible && leftWrist) {
215
+ if (shoulders.leftShoulder && leftWrist.y < shoulders.leftShoulder.y) {
216
+ positions.push('Left arm raised');
217
+ } else if (elbows.leftElbow && leftWrist.y < elbows.leftElbow.y) {
218
+ positions.push('Left arm extended');
219
+ } else {
220
+ positions.push('Left wrist visible');
221
+ }
222
+ }
223
+
224
+ // Check right hand position
225
+ if (rightWristVisible && rightWrist) {
226
+ if (shoulders.rightShoulder && rightWrist.y < shoulders.rightShoulder.y) {
227
+ positions.push('Right arm raised');
228
+ } else if (elbows.rightElbow && rightWrist.y < elbows.rightElbow.y) {
229
+ positions.push('Right arm extended');
230
+ } else {
231
+ positions.push('Right wrist visible');
232
+ }
233
+ }
234
+
235
+ return positions.length > 0 ? positions.join(', ') : '';
236
+ }
237
+
238
+ /**
239
+ * Get the stored reference pose
240
+ */
241
+ getReferencePose(): ReferencePose | null {
242
+ return this.referencePose;
243
+ }
244
+
245
+ /**
246
+ * Clear the stored reference pose
247
+ */
248
+ clearReferencePose(): void {
249
+ this.referencePose = null;
250
+ console.log('[Reference Pose] Reference pose cleared');
251
+ }
252
+
253
+ /**
254
+ * Convert File to base64 string
255
+ */
256
+ private fileToBase64(file: File): Promise<string> {
257
+ return new Promise((resolve, reject) => {
258
+ const reader = new FileReader();
259
+
260
+ reader.onload = () => {
261
+ resolve(reader.result as string);
262
+ };
263
+
264
+ reader.onerror = () => {
265
+ reject(new Error('Failed to read file'));
266
+ };
267
+
268
+ reader.readAsDataURL(file);
269
+ });
270
+ }
271
+ }
@@ -0,0 +1,183 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { PoseSelectionService } from './pose-selection.service';
4
+ import { POSE_DEFINITIONS } from '../models/constants/pose-definitions.constant';
5
+ import type { PoseCategory } from '../types/pose-category.type';
6
+ import type { PoseDifficulty } from '../types/pose-difficulty.type';
7
+ import type { DetectionStrategy } from '../types/detection-strategy.type';
8
+
9
+ describe('PoseSelectionService', () => {
10
+ let service: PoseSelectionService;
11
+
12
+ beforeEach(() => {
13
+ TestBed.configureTestingModule({
14
+ providers: [PoseSelectionService]
15
+ });
16
+ service = TestBed.inject(PoseSelectionService);
17
+ });
18
+
19
+ it('should be created', () => {
20
+ expect(service).toBeTruthy();
21
+ });
22
+
23
+ it('getAllPoses returns all pose definitions', () => {
24
+ const poses = service.getAllPoses();
25
+ expect(poses).toEqual(POSE_DEFINITIONS);
26
+ expect(poses.length).toBeGreaterThan(0);
27
+ });
28
+
29
+ it('getPosesByCategory filters by category', () => {
30
+ const facePoses = service.getPosesByCategory('face' as PoseCategory);
31
+ expect(facePoses.every((p) => p.category === 'face')).toBe(true);
32
+ expect(facePoses.length).toBeLessThanOrEqual(POSE_DEFINITIONS.length);
33
+ });
34
+
35
+ it('getPosesByDifficulty filters by difficulty', () => {
36
+ const easyPoses = service.getPosesByDifficulty('easy' as PoseDifficulty);
37
+ expect(easyPoses.every((p) => p.difficulty === 'easy')).toBe(true);
38
+ });
39
+
40
+ it('getPosesByDetectionStrategy filters by strategy', () => {
41
+ const faceApiPoses = service.getPosesByDetectionStrategy('face-api' as DetectionStrategy);
42
+ expect(faceApiPoses.every((p) => p.detectionStrategy === 'face-api')).toBe(true);
43
+ });
44
+
45
+ it('selectPose by ID returns true when pose exists', () => {
46
+ const poseId = POSE_DEFINITIONS[0].id;
47
+ const result = service.selectPose(poseId);
48
+ expect(result).toBe(true);
49
+ const selected = service.getSelectedPose();
50
+ expect(selected).toBeTruthy();
51
+ expect(selected!.pose.id).toBe(poseId);
52
+ });
53
+
54
+ it('selectPose by ID returns false when pose not found', () => {
55
+ const result = service.selectPose(99999);
56
+ expect(result).toBe(false);
57
+ });
58
+
59
+ it('selectPoseByObject sets selected pose', () => {
60
+ const pose = POSE_DEFINITIONS[0];
61
+ service.selectPoseByObject(pose);
62
+ const selected = service.getSelectedPose();
63
+ expect(selected).toBeTruthy();
64
+ expect(selected!.pose).toEqual(pose);
65
+ expect(selected!.imageUrl).toBe(pose.imagePath);
66
+ });
67
+
68
+ it('getSelectedPose returns null when nothing selected', () => {
69
+ service.clearSelection();
70
+ expect(service.getSelectedPose()).toBeNull();
71
+ });
72
+
73
+ it('clearSelection clears the selection', () => {
74
+ service.selectPose(POSE_DEFINITIONS[0].id);
75
+ service.clearSelection();
76
+ expect(service.getSelectedPose()).toBeNull();
77
+ });
78
+
79
+ it('hasSelection returns true when pose selected', () => {
80
+ service.selectPose(POSE_DEFINITIONS[0].id);
81
+ expect(service.hasSelection()).toBe(true);
82
+ });
83
+
84
+ it('hasSelection returns false when no selection', () => {
85
+ service.clearSelection();
86
+ expect(service.hasSelection()).toBe(false);
87
+ });
88
+
89
+ it('getRandomPose returns a pose from definitions', () => {
90
+ const pose = service.getRandomPose();
91
+ expect(pose).toBeTruthy();
92
+ expect(POSE_DEFINITIONS).toContain(pose);
93
+ });
94
+
95
+ it('getRandomPoses returns requested count', () => {
96
+ const poses = service.getRandomPoses(3);
97
+ expect(poses.length).toBe(3);
98
+ poses.forEach((p) => expect(POSE_DEFINITIONS).toContain(p));
99
+ });
100
+
101
+ it('getRandomPoses caps at available count', () => {
102
+ const poses = service.getRandomPoses(9999);
103
+ expect(poses.length).toBeLessThanOrEqual(POSE_DEFINITIONS.length);
104
+ });
105
+
106
+ it('getEasyPoses returns face/head poses with easy difficulty', () => {
107
+ const poses = service.getEasyPoses();
108
+ expect(poses.every((p) => p.difficulty === 'easy')).toBe(true);
109
+ expect(poses.every((p) => p.category === 'face' || p.category === 'head')).toBe(true);
110
+ });
111
+
112
+ it('getAdvancedPoses returns gesture or combined poses', () => {
113
+ const poses = service.getAdvancedPoses();
114
+ expect(poses.every((p) => p.category === 'gesture' || p.category === 'combined')).toBe(true);
115
+ });
116
+
117
+ it('searchPoses finds by name', () => {
118
+ const query = POSE_DEFINITIONS[0].name.toLowerCase().split(' ')[0];
119
+ const poses = service.searchPoses(query);
120
+ expect(poses.length).toBeGreaterThan(0);
121
+ expect(poses.some((p) => p.name.toLowerCase().includes(query))).toBe(true);
122
+ });
123
+
124
+ it('searchPoses finds by description', () => {
125
+ const poses = service.searchPoses('smile');
126
+ expect(poses.some((p) => p.description.toLowerCase().includes('smile') || p.name.toLowerCase().includes('smile'))).toBe(true);
127
+ });
128
+
129
+ it('selectedPose$ emits when selection changes', (done) => {
130
+ service.selectedPose$.subscribe((selected) => {
131
+ if (selected) {
132
+ expect(selected.pose.id).toBe(POSE_DEFINITIONS[0].id);
133
+ done();
134
+ }
135
+ });
136
+ service.selectPose(POSE_DEFINITIONS[0].id);
137
+ });
138
+
139
+ it('selectPoseByObject resolves raw base64 PNG to data URI', () => {
140
+ const pose = { ...POSE_DEFINITIONS[0], imagePath: 'iVBORw0KGgoAAAANSUhEUg==' };
141
+ service.selectPoseByObject(pose);
142
+ const selected = service.getSelectedPose();
143
+ expect(selected!.imageUrl).toBe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==');
144
+ });
145
+
146
+ it('selectPoseByObject resolves raw base64 JPEG to data URI', () => {
147
+ const pose = { ...POSE_DEFINITIONS[0], imagePath: '/9j/4AAQSkZJRg==' };
148
+ service.selectPoseByObject(pose);
149
+ const selected = service.getSelectedPose();
150
+ expect(selected!.imageUrl).toBe('data:image/jpeg;base64,/9j/4AAQSkZJRg==');
151
+ });
152
+
153
+ it('selectPoseByObject passes through data URI unchanged', () => {
154
+ const dataUri = 'data:image/png;base64,iVBORw0KGgo==';
155
+ const pose = { ...POSE_DEFINITIONS[0], imagePath: dataUri };
156
+ service.selectPoseByObject(pose);
157
+ const selected = service.getSelectedPose();
158
+ expect(selected!.imageUrl).toBe(dataUri);
159
+ });
160
+
161
+ it('selectPoseByObject passes through asset paths unchanged', () => {
162
+ const pose = POSE_DEFINITIONS[0];
163
+ service.selectPoseByObject(pose);
164
+ const selected = service.getSelectedPose();
165
+ expect(selected!.imageUrl).toBe(pose.imagePath);
166
+ });
167
+
168
+ it('selectPoseByObject passes through http URLs unchanged', () => {
169
+ const url = 'https://example.com/image.png';
170
+ const pose = { ...POSE_DEFINITIONS[0], imagePath: url };
171
+ service.selectPoseByObject(pose);
172
+ const selected = service.getSelectedPose();
173
+ expect(selected!.imageUrl).toBe(url);
174
+ });
175
+
176
+ it('selectPoseByObject prefers imageBlobUrl over imagePath', () => {
177
+ const blobUrl = 'blob:http://localhost/abc-123';
178
+ const pose = { ...POSE_DEFINITIONS[0], imageBlobUrl: blobUrl };
179
+ service.selectPoseByObject(pose);
180
+ const selected = service.getSelectedPose();
181
+ expect(selected!.imageUrl).toBe(blobUrl);
182
+ });
183
+ });
@@ -0,0 +1,179 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { BehaviorSubject, Observable } from 'rxjs';
3
+ import { PoseDefinition } from '../interfaces/pose-definition.interface';
4
+ import { PoseCategory } from '../types/pose-category.type';
5
+ import { PoseDifficulty } from '../types/pose-difficulty.type';
6
+ import { DetectionStrategy } from '../types/detection-strategy.type';
7
+ import { POSE_DEFINITIONS } from '../models/constants/pose-definitions.constant';
8
+ import { getPoseById } from '../models/utils/pose.utils';
9
+
10
+ export interface SelectedPoseInfo {
11
+ pose: PoseDefinition;
12
+ imageUrl: string;
13
+ timestamp: Date;
14
+ }
15
+
16
+ @Injectable({
17
+ providedIn: 'root'
18
+ })
19
+ export class PoseSelectionService {
20
+ private selectedPoseSubject = new BehaviorSubject<SelectedPoseInfo | null>(null);
21
+ public selectedPose$: Observable<SelectedPoseInfo | null> = this.selectedPoseSubject.asObservable();
22
+
23
+ constructor() {}
24
+
25
+ /**
26
+ * Get all available poses
27
+ */
28
+ getAllPoses(): PoseDefinition[] {
29
+ return POSE_DEFINITIONS;
30
+ }
31
+
32
+ /**
33
+ * Get poses filtered by category
34
+ */
35
+ getPosesByCategory(category: PoseCategory): PoseDefinition[] {
36
+ return POSE_DEFINITIONS.filter(pose => pose.category === category);
37
+ }
38
+
39
+ /**
40
+ * Get poses filtered by difficulty
41
+ */
42
+ getPosesByDifficulty(difficulty: PoseDifficulty): PoseDefinition[] {
43
+ return POSE_DEFINITIONS.filter(pose => pose.difficulty === difficulty);
44
+ }
45
+
46
+ /**
47
+ * Get poses filtered by detection strategy
48
+ */
49
+ getPosesByDetectionStrategy(strategy: DetectionStrategy): PoseDefinition[] {
50
+ return POSE_DEFINITIONS.filter(pose => pose.detectionStrategy === strategy);
51
+ }
52
+
53
+ /**
54
+ * Select a pose by ID
55
+ */
56
+ selectPose(poseId: number): boolean {
57
+ const pose = getPoseById(poseId);
58
+
59
+ if (!pose) {
60
+ console.error('[PoseSelectionService] Pose not found:', poseId);
61
+ return false;
62
+ }
63
+
64
+ const selectedPoseInfo: SelectedPoseInfo = {
65
+ pose,
66
+ imageUrl: this.resolveImageUrl(pose.imageBlobUrl || pose.imagePath),
67
+ timestamp: new Date()
68
+ };
69
+
70
+ this.selectedPoseSubject.next(selectedPoseInfo);
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Select a pose by object
76
+ */
77
+ selectPoseByObject(pose: PoseDefinition): void {
78
+ const selectedPoseInfo: SelectedPoseInfo = {
79
+ pose,
80
+ imageUrl: this.resolveImageUrl(pose.imageBlobUrl || pose.imagePath),
81
+ timestamp: new Date()
82
+ };
83
+
84
+ this.selectedPoseSubject.next(selectedPoseInfo);
85
+ }
86
+
87
+ /**
88
+ * Resolve an image source to a renderable URL.
89
+ * Handles: full URLs, data URIs, relative asset paths, and raw base64 strings.
90
+ */
91
+ private resolveImageUrl(src: string): string {
92
+ if (!src) return '';
93
+ if (src.startsWith('data:') || src.startsWith('blob:') || src.startsWith('http://') || src.startsWith('https://') || src.startsWith('./')) {
94
+ return src;
95
+ }
96
+ const base64Match = this.detectBase64Mime(src);
97
+ if (base64Match) {
98
+ return `data:${base64Match};base64,${src}`;
99
+ }
100
+ return src;
101
+ }
102
+
103
+ private detectBase64Mime(src: string): string | null {
104
+ if (src.startsWith('iVBOR')) return 'image/png';
105
+ if (src.startsWith('/9j/')) return 'image/jpeg';
106
+ if (src.startsWith('R0lGOD')) return 'image/gif';
107
+ if (src.startsWith('UklGR')) return 'image/webp';
108
+ if (src.length > 100 && /^[A-Za-z0-9+/=]+$/.test(src.substring(0, 64))) return 'image/png';
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Get currently selected pose
114
+ */
115
+ getSelectedPose(): SelectedPoseInfo | null {
116
+ return this.selectedPoseSubject.value;
117
+ }
118
+
119
+ /**
120
+ * Clear selected pose
121
+ */
122
+ clearSelection(): void {
123
+ this.selectedPoseSubject.next(null);
124
+ }
125
+
126
+ /**
127
+ * Check if a pose is currently selected
128
+ */
129
+ hasSelection(): boolean {
130
+ return this.selectedPoseSubject.value !== null;
131
+ }
132
+
133
+ /**
134
+ * Get random pose
135
+ */
136
+ getRandomPose(): PoseDefinition {
137
+ const randomIndex = Math.floor(Math.random() * POSE_DEFINITIONS.length);
138
+ return POSE_DEFINITIONS[randomIndex];
139
+ }
140
+
141
+ /**
142
+ * Get random poses by count
143
+ */
144
+ getRandomPoses(count: number): PoseDefinition[] {
145
+ const shuffled = [...POSE_DEFINITIONS].sort(() => 0.5 - Math.random());
146
+ return shuffled.slice(0, Math.min(count, POSE_DEFINITIONS.length));
147
+ }
148
+
149
+ /**
150
+ * Get poses suitable for easy verification
151
+ */
152
+ getEasyPoses(): PoseDefinition[] {
153
+ return POSE_DEFINITIONS.filter(
154
+ pose => pose.difficulty === 'easy' &&
155
+ (pose.category === 'face' || pose.category === 'head')
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Get poses suitable for advanced verification
161
+ */
162
+ getAdvancedPoses(): PoseDefinition[] {
163
+ return POSE_DEFINITIONS.filter(
164
+ pose => pose.category === 'gesture' || pose.category === 'combined'
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Search poses by name or description
170
+ */
171
+ searchPoses(query: string): PoseDefinition[] {
172
+ const lowerQuery = query.toLowerCase();
173
+ return POSE_DEFINITIONS.filter(
174
+ pose =>
175
+ pose.name.toLowerCase().includes(lowerQuery) ||
176
+ pose.description.toLowerCase().includes(lowerQuery)
177
+ );
178
+ }
179
+ }