Browse Source

face recognition integration

Dr-Swopt 1 week ago
parent
commit
37e20e994c

+ 6 - 2
nest-cli.json

@@ -3,6 +3,10 @@
   "collection": "@nestjs/schematics",
   "sourceRoot": "src",
   "compilerOptions": {
-    "deleteOutDir": true
+    "deleteOutDir": true,
+    "assets": [
+      "**/*.proto"
+    ],
+    "watchAssets": true
   }
-}
+}

+ 140 - 16
package-lock.json

@@ -9,9 +9,12 @@
       "version": "0.0.1",
       "license": "UNLICENSED",
       "dependencies": {
+        "@grpc/grpc-js": "^1.14.2",
+        "@grpc/proto-loader": "^0.8.0",
         "@nestjs/common": "^11.1.3",
         "@nestjs/core": "^11.1.3",
         "@nestjs/jwt": "^11.0.0",
+        "@nestjs/microservices": "^11.1.9",
         "@nestjs/mongoose": "^11.0.3",
         "@nestjs/passport": "^11.0.5",
         "@nestjs/platform-express": "^11.1.3",
@@ -972,6 +975,68 @@
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
+    "node_modules/@grpc/grpc-js": {
+      "version": "1.14.2",
+      "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.2.tgz",
+      "integrity": "sha512-QzVUtEFyu05UNx2xr0fCQmStUO17uVQhGNowtxs00IgTZT6/W2PBLfUkj30s0FKJ29VtTa3ArVNIhNP6akQhqA==",
+      "license": "Apache-2.0",
+      "peer": true,
+      "dependencies": {
+        "@grpc/proto-loader": "^0.8.0",
+        "@js-sdsl/ordered-map": "^4.4.2"
+      },
+      "engines": {
+        "node": ">=12.10.0"
+      }
+    },
+    "node_modules/@grpc/proto-loader": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz",
+      "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "lodash.camelcase": "^4.3.0",
+        "long": "^5.0.0",
+        "protobufjs": "^7.5.3",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@grpc/proto-loader/node_modules/long": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+      "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@grpc/proto-loader/node_modules/protobufjs": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
+      "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.2",
+        "@protobufjs/base64": "^1.1.2",
+        "@protobufjs/codegen": "^2.0.4",
+        "@protobufjs/eventemitter": "^1.1.0",
+        "@protobufjs/fetch": "^1.1.0",
+        "@protobufjs/float": "^1.0.2",
+        "@protobufjs/inquire": "^1.1.0",
+        "@protobufjs/path": "^1.1.2",
+        "@protobufjs/pool": "^1.1.0",
+        "@protobufjs/utf8": "^1.1.0",
+        "@types/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/@hexagon/base64": {
       "version": "1.1.28",
       "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
@@ -2078,6 +2143,16 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@js-sdsl/ordered-map": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+      "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/js-sdsl"
+      }
+    },
     "node_modules/@levischuck/tiny-cbor": {
       "version": "0.2.11",
       "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
@@ -2723,6 +2798,65 @@
         "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
       }
     },
+    "node_modules/@nestjs/microservices": {
+      "version": "11.1.9",
+      "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.1.9.tgz",
+      "integrity": "sha512-2n9ftdRej+vv+MMO863dDvxBaJ/VHkzroo/Nyl59mE23mCYno3NZEn0QgCYByktoE8r7Cc0kSOqrTVE++KALug==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "iterare": "1.2.1",
+        "tslib": "2.8.1"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nest"
+      },
+      "peerDependencies": {
+        "@grpc/grpc-js": "*",
+        "@nestjs/common": "^11.0.0",
+        "@nestjs/core": "^11.0.0",
+        "@nestjs/websockets": "^11.0.0",
+        "amqp-connection-manager": "*",
+        "amqplib": "*",
+        "cache-manager": "*",
+        "ioredis": "*",
+        "kafkajs": "*",
+        "mqtt": "*",
+        "nats": "*",
+        "reflect-metadata": "^0.1.12 || ^0.2.0",
+        "rxjs": "^7.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@grpc/grpc-js": {
+          "optional": true
+        },
+        "@nestjs/websockets": {
+          "optional": true
+        },
+        "amqp-connection-manager": {
+          "optional": true
+        },
+        "amqplib": {
+          "optional": true
+        },
+        "cache-manager": {
+          "optional": true
+        },
+        "ioredis": {
+          "optional": true
+        },
+        "kafkajs": {
+          "optional": true
+        },
+        "mqtt": {
+          "optional": true
+        },
+        "nats": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@nestjs/mongoose": {
       "version": "11.0.3",
       "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-11.0.3.tgz",
@@ -5393,7 +5527,6 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^2.0.1"
@@ -6242,7 +6375,6 @@
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
       "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
-      "dev": true,
       "license": "ISC",
       "dependencies": {
         "string-width": "^4.2.0",
@@ -6257,7 +6389,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -6267,7 +6398,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -6280,7 +6410,6 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.0.0",
@@ -6822,7 +6951,6 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/encodeurl": {
@@ -7013,7 +7141,6 @@
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
       "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -8007,7 +8134,6 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true,
       "license": "ISC",
       "engines": {
         "node": "6.* || 8.* || >= 10.*"
@@ -8486,7 +8612,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -9730,6 +9855,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash.camelcase": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+      "license": "MIT"
+    },
     "node_modules/lodash.includes": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -11435,7 +11566,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -12276,7 +12406,6 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -12330,7 +12459,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -12340,7 +12468,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -13792,7 +13919,6 @@
       "version": "5.0.8",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
       "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
-      "dev": true,
       "license": "ISC",
       "engines": {
         "node": ">=10"
@@ -13809,7 +13935,6 @@
       "version": "17.7.2",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
       "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "cliui": "^8.0.1",
@@ -13828,7 +13953,6 @@
       "version": "21.1.1",
       "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
       "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
-      "dev": true,
       "license": "ISC",
       "engines": {
         "node": ">=12"

+ 3 - 0
package.json

@@ -20,9 +20,12 @@
     "test:e2e": "jest --config ./test/jest-e2e.json"
   },
   "dependencies": {
+    "@grpc/grpc-js": "^1.14.2",
+    "@grpc/proto-loader": "^0.8.0",
     "@nestjs/common": "^11.1.3",
     "@nestjs/core": "^11.1.3",
     "@nestjs/jwt": "^11.0.0",
+    "@nestjs/microservices": "^11.1.9",
     "@nestjs/mongoose": "^11.0.3",
     "@nestjs/passport": "^11.0.5",
     "@nestjs/platform-express": "^11.1.3",

+ 3 - 1
src/app.module.ts

@@ -9,6 +9,7 @@ import { ServiceModule } from './services/service.module';
 import { ActivityModule } from './activity/activity.module';
 import { MongoModule } from './mongo/mongo.module';
 import { FFBProductionModule } from './FFB/ffb-production.module';
+import { FaceModule } from './face/face.module';
 
 @Module({
   imports: [
@@ -20,7 +21,8 @@ import { FFBProductionModule } from './FFB/ffb-production.module';
     AuthModule,
     PlantationTreeModule,
     ServiceModule,
-    ActivityModule
+    ActivityModule,
+    FaceModule
   ],
   controllers: [AppController],
   providers: [AppService],

+ 22 - 0
src/face/face.controller.ts

@@ -0,0 +1,22 @@
+import { Controller, Post, Body } from '@nestjs/common';
+import { FaceService } from './face.service';
+
+@Controller('face')
+export class FaceController {
+  constructor(private readonly faceService: FaceService) { }
+
+  @Post('scan')
+  async scan(@Body() body: { imageBase64: string }) {
+    console.log(`Scanning....`)
+    const buffer = Buffer.from(body.imageBase64, 'base64');
+    return this.faceService.recognizeFace(buffer);
+  }
+
+  @Post('enroll')
+  async enroll(@Body() body: { imageBase64: string; name: string }) {
+    const buffer = Buffer.from(body.imageBase64, 'base64');
+    const name = body.name; // <-- extract from body
+    return this.faceService.enrollFace(buffer, name);
+  }
+
+}

+ 26 - 0
src/face/face.module.ts

@@ -0,0 +1,26 @@
+import { join } from 'path';
+import { FaceService } from './face.service';
+import { Module } from '@nestjs/common';
+import { ClientsModule, Transport } from '@nestjs/microservices';
+import { FaceController } from './face.controller';
+
+@Module({
+  controllers: [FaceController],
+  imports: [
+    ClientsModule.register([
+      {
+        name: 'FACE_RECOGNITION_PACKAGE',
+        transport: Transport.GRPC,
+        options: {
+          package: 'facerecognition',  // must match `package` in proto
+          protoPath: join(__dirname, 'proto/face_recognition.proto'),
+          url: 'localhost:50051',       // your Python gRPC server
+        },
+      },
+    ]),
+    // ... other imports
+  ],
+  providers: [FaceService],
+  exports: [FaceService],
+})
+export class FaceModule { }

+ 24 - 0
src/face/face.service.ts

@@ -0,0 +1,24 @@
+import { Injectable, OnModuleInit, Inject } from '@nestjs/common';
+import { ClientGrpc } from '@nestjs/microservices';
+
+@Injectable()
+export class FaceService implements OnModuleInit {
+  private faceGrpc: any;
+
+  constructor(
+    @Inject('FACE_RECOGNITION_PACKAGE')
+    private readonly client: ClientGrpc,  // ✅ correct type
+  ) { }
+
+  onModuleInit() {
+    this.faceGrpc = this.client.getService<any>('FaceRecognitionService');
+  }
+
+  async recognizeFace(imageBuffer: Buffer) {
+    return this.faceGrpc.Recognize({ image: imageBuffer }).toPromise();
+  }
+
+  async enrollFace(imageBuffer: Buffer, name: string) {
+    return this.faceGrpc.EnrollFace({ image: imageBuffer, name }).toPromise();
+  }
+}

+ 31 - 0
src/face/proto/face_recognition.proto

@@ -0,0 +1,31 @@
+syntax = "proto3";
+
+package facerecognition;
+
+// The request message containing the image bytes
+message FaceRequest {
+  bytes image = 1;
+}
+
+// The response message containing recognition results
+message FaceResponse {
+  string name = 1;
+  float confidence = 2;
+}
+
+// New messages for enrollment
+message EnrollFaceRequest {
+  bytes image = 1;
+  string name = 2;
+}
+
+message EnrollFaceResponse {
+  bool success = 1;
+  string message = 2;
+}
+
+// The Face Recognition Service
+service FaceRecognitionService {
+  rpc Recognize(FaceRequest) returns (FaceResponse);
+  rpc EnrollFace(EnrollFaceRequest) returns (EnrollFaceResponse);
+}

+ 13 - 1
tsconfig.build.json

@@ -1,4 +1,16 @@
 {
   "extends": "./tsconfig.json",
-  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+  "compilerOptions": {
+    "outDir": "./dist",
+    "rootDir": "./src"
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.proto"
+  ],
+  "exclude": [
+    "node_modules",
+    "test",
+    "**/*spec.ts"
+  ]
 }

File diff suppressed because it is too large
+ 0 - 0
tsconfig.build.tsbuildinfo


Some files were not shown because too many files changed in this diff