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.
- package/.browserslistrc +15 -0
- package/.dockerignore +48 -0
- package/.editorconfig +16 -0
- package/.eslintrc.json +47 -0
- package/.vercelignore +7 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +3 -0
- package/DOCKER.md +221 -0
- package/Dockerfile +33 -0
- package/README.md +268 -0
- package/angular.json +156 -0
- package/capacitor.config.ts +9 -0
- package/docker-compose.dev.yml +20 -0
- package/docker-compose.yml +18 -0
- package/ionic.config.json +7 -0
- package/jest.config.js +38 -0
- package/nginx.conf +50 -0
- package/package.json +131 -0
- package/patches/ng-packagr+20.3.2.patch +60 -0
- package/projects/facecog-liveness-verification/README.md +295 -0
- package/projects/facecog-liveness-verification/ng-package.json +7 -0
- package/projects/facecog-liveness-verification/package.json +48 -0
- package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
- package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
- package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
- package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
- package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
- package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
- package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
- package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
- package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
- package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
- package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
- package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
- package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
- package/projects/facecog-liveness-verification-test/src/index.html +31 -0
- package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
- package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
- package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
- package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
- package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
- package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
- package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
- package/setup-jest.ts +118 -0
- package/tsconfig.json +41 -0
- package/tsconfig.spec.json +15 -0
- 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
|
+
}
|
package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts
ADDED
|
@@ -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
|
+
}
|