@arkyc/face-match 1.2.10 → 2.0.0
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/README.md +1 -1
- package/dist/drivers/local.d.mts +23 -0
- package/dist/drivers/local.d.mts.map +1 -0
- package/dist/drivers/local.mjs +83 -0
- package/dist/drivers/local.mjs.map +1 -0
- package/dist/drivers/mock.d.mts +3 -1
- package/dist/drivers/mock.d.mts.map +1 -1
- package/dist/drivers/mock.mjs +18 -0
- package/dist/drivers/mock.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/registry.d.mts.map +1 -1
- package/dist/registry.mjs +2 -0
- package/dist/registry.mjs.map +1 -1
- package/dist/types.d.mts +14 -2
- package/dist/types.d.mts.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Driver-based face matching for [Arkyc](https://github.com/arkstack-hq/arkyc) — an
|
|
4
4
|
open-source, multi-tenant identity-verification platform. Compares the document
|
|
5
|
-
portrait against the selfie; the active driver (`mock` or `
|
|
5
|
+
portrait against the selfie; the active driver (`mock`, `external` HTTP, or `local` ONNX models) is
|
|
6
6
|
chosen by config behind a stable result contract.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FaceEmbedding, FaceMatchConfig, FaceMatchDriver, FaceMatchRequest } from "../types.mjs";
|
|
2
|
+
import { FaceMatchResultData } from "@arkyc/types";
|
|
3
|
+
|
|
4
|
+
//#region src/drivers/local.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Face matching in-process with open models: YuNet finds and aligns the face,
|
|
7
|
+
* SFace embeds it, and the two embeddings are compared by cosine similarity.
|
|
8
|
+
* The similarity is mapped to a score where the match threshold sits at 0.6.
|
|
9
|
+
*/
|
|
10
|
+
declare class LocalFaceMatchDriver implements FaceMatchDriver {
|
|
11
|
+
readonly name = "local";
|
|
12
|
+
private loaded?;
|
|
13
|
+
private readonly store;
|
|
14
|
+
constructor(config: FaceMatchConfig);
|
|
15
|
+
compare(request: FaceMatchRequest): Promise<FaceMatchResultData>;
|
|
16
|
+
embed(image: Uint8Array): Promise<(FaceEmbedding & {
|
|
17
|
+
detection: number;
|
|
18
|
+
}) | null>;
|
|
19
|
+
private models;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { LocalFaceMatchDriver };
|
|
23
|
+
//# sourceMappingURL=local.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.mts","names":[],"sources":["../../src/drivers/local.ts"],"mappings":";;;;;;AAsBA;;;cAAa,oBAAA,YAAgC,eAAA;EAAA,SAClC,IAAA;EAAA,QAED,MAAA;EAAA,iBACS,KAAA;cAEL,MAAA,EAAQ,eAAA;EAKd,OAAA,CAAQ,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,mBAAA;EAqB5C,KAAA,CAAM,KAAA,EAAO,UAAA,GAAa,OAAA,EAAS,aAAA;IAAkB,SAAA;EAAA;EAAA,QAoBnD,MAAA;AAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ALIGNED_SIZE, FaceDetector, ModelStore, OnnxModel, alignFace, decodeImage, toTensor } from "@arkyc/vision";
|
|
2
|
+
//#region src/drivers/local.ts
|
|
3
|
+
/** Cosine similarity at which SFace embeddings are the same person (OpenCV's calibrated value). */
|
|
4
|
+
const SFACE_MATCH = .363;
|
|
5
|
+
const MODEL = "sface-2021dec";
|
|
6
|
+
/**
|
|
7
|
+
* Face matching in-process with open models: YuNet finds and aligns the face,
|
|
8
|
+
* SFace embeds it, and the two embeddings are compared by cosine similarity.
|
|
9
|
+
* The similarity is mapped to a score where the match threshold sits at 0.6.
|
|
10
|
+
*/
|
|
11
|
+
var LocalFaceMatchDriver = class {
|
|
12
|
+
name = "local";
|
|
13
|
+
loaded;
|
|
14
|
+
store;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
if (!config.modelDirectory) throw new Error("LocalFaceMatchDriver requires config.modelDirectory");
|
|
17
|
+
this.store = new ModelStore(config.modelDirectory);
|
|
18
|
+
}
|
|
19
|
+
async compare(request) {
|
|
20
|
+
const [portrait, selfie] = await Promise.all([this.embed(request.documentPortrait), this.embed(request.selfie)]);
|
|
21
|
+
if (!portrait || !selfie) return {
|
|
22
|
+
passed: false,
|
|
23
|
+
similarityScore: 0,
|
|
24
|
+
confidence: 0,
|
|
25
|
+
raw: {
|
|
26
|
+
provider: "local",
|
|
27
|
+
model: MODEL,
|
|
28
|
+
faceFound: {
|
|
29
|
+
portrait: !!portrait,
|
|
30
|
+
selfie: !!selfie
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const cosine = portrait.vector.reduce((sum, value, i) => sum + value * selfie.vector[i], 0);
|
|
35
|
+
return {
|
|
36
|
+
passed: cosine >= SFACE_MATCH,
|
|
37
|
+
similarityScore: clamp01(.6 + (cosine - SFACE_MATCH) * 1.5),
|
|
38
|
+
confidence: Math.min(portrait.detection, selfie.detection),
|
|
39
|
+
raw: {
|
|
40
|
+
provider: "local",
|
|
41
|
+
model: MODEL,
|
|
42
|
+
cosine
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async embed(image) {
|
|
47
|
+
if (!image.length) return null;
|
|
48
|
+
const { detector, recognizer } = await this.models();
|
|
49
|
+
const decoded = await decodeImage(image);
|
|
50
|
+
const face = largest(await detector.detect(decoded));
|
|
51
|
+
if (!face) return null;
|
|
52
|
+
const aligned = alignFace(decoded, face);
|
|
53
|
+
const output = Object.values(await recognizer.run(toTensor(aligned, "rgb"), [
|
|
54
|
+
1,
|
|
55
|
+
3,
|
|
56
|
+
ALIGNED_SIZE,
|
|
57
|
+
ALIGNED_SIZE
|
|
58
|
+
]))[0];
|
|
59
|
+
const norm = Math.hypot(...output) || 1;
|
|
60
|
+
return {
|
|
61
|
+
vector: Array.from(output, (value) => value / norm),
|
|
62
|
+
model: MODEL,
|
|
63
|
+
matchThreshold: SFACE_MATCH,
|
|
64
|
+
detection: face.score
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
models() {
|
|
68
|
+
this.loaded ??= (async () => ({
|
|
69
|
+
detector: await FaceDetector.load(this.store.path("yunet")),
|
|
70
|
+
recognizer: await OnnxModel.load(this.store.path("sface"))
|
|
71
|
+
}))();
|
|
72
|
+
return this.loaded;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/** The face with the largest box. */
|
|
76
|
+
function largest(faces) {
|
|
77
|
+
return faces.reduce((best, face) => !best || face.box[2] * face.box[3] > best.box[2] * best.box[3] ? face : best, null);
|
|
78
|
+
}
|
|
79
|
+
const clamp01 = (n) => Math.min(1, Math.max(0, n));
|
|
80
|
+
//#endregion
|
|
81
|
+
export { LocalFaceMatchDriver };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=local.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.mjs","names":[],"sources":["../../src/drivers/local.ts"],"sourcesContent":["import type { FaceMatchResultData } from '@arkyc/types'\nimport {\n ALIGNED_SIZE,\n FaceDetector,\n ModelStore,\n OnnxModel,\n alignFace,\n decodeImage,\n toTensor,\n type DetectedFace,\n} from '@arkyc/vision'\nimport type { FaceEmbedding, FaceMatchConfig, FaceMatchDriver, FaceMatchRequest } from '../types'\n\n/** Cosine similarity at which SFace embeddings are the same person (OpenCV's calibrated value). */\nconst SFACE_MATCH = 0.363\nconst MODEL = 'sface-2021dec'\n\n/**\n * Face matching in-process with open models: YuNet finds and aligns the face,\n * SFace embeds it, and the two embeddings are compared by cosine similarity.\n * The similarity is mapped to a score where the match threshold sits at 0.6.\n */\nexport class LocalFaceMatchDriver implements FaceMatchDriver {\n readonly name = 'local'\n\n private loaded?: Promise<{ detector: FaceDetector; recognizer: OnnxModel }>\n private readonly store: ModelStore\n\n constructor(config: FaceMatchConfig) {\n if (!config.modelDirectory) throw new Error('LocalFaceMatchDriver requires config.modelDirectory')\n this.store = new ModelStore(config.modelDirectory)\n }\n\n async compare(request: FaceMatchRequest): Promise<FaceMatchResultData> {\n const [portrait, selfie] = await Promise.all([this.embed(request.documentPortrait), this.embed(request.selfie)])\n if (!portrait || !selfie) {\n return {\n passed: false,\n similarityScore: 0,\n confidence: 0,\n raw: { provider: 'local', model: MODEL, faceFound: { portrait: !!portrait, selfie: !!selfie } },\n }\n }\n\n const cosine = portrait.vector.reduce((sum, value, i) => sum + value * selfie.vector[i]!, 0)\n\n return {\n passed: cosine >= SFACE_MATCH,\n similarityScore: clamp01(0.6 + (cosine - SFACE_MATCH) * 1.5),\n confidence: Math.min(portrait.detection, selfie.detection),\n raw: { provider: 'local', model: MODEL, cosine },\n }\n }\n\n async embed(image: Uint8Array): Promise<(FaceEmbedding & { detection: number }) | null> {\n if (!image.length) return null\n\n const { detector, recognizer } = await this.models()\n const decoded = await decodeImage(image)\n const face = largest(await detector.detect(decoded))\n if (!face) return null\n\n const aligned = alignFace(decoded, face)\n const output = Object.values(await recognizer.run(toTensor(aligned, 'rgb'), [1, 3, ALIGNED_SIZE, ALIGNED_SIZE]))[0]!\n const norm = Math.hypot(...output) || 1\n\n return {\n vector: Array.from(output, (value) => value / norm),\n model: MODEL,\n matchThreshold: SFACE_MATCH,\n detection: face.score,\n }\n }\n\n private models() {\n this.loaded ??= (async () => ({\n detector: await FaceDetector.load(this.store.path('yunet')),\n recognizer: await OnnxModel.load(this.store.path('sface')),\n }))()\n\n return this.loaded\n }\n}\n\n/** The face with the largest box. */\nexport function largest(faces: DetectedFace[]): DetectedFace | null {\n return faces.reduce<DetectedFace | null>(\n (best, face) => (!best || face.box[2] * face.box[3] > best.box[2] * best.box[3] ? face : best),\n null,\n )\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n))\n"],"mappings":";;;AAcA,MAAM,cAAc;AACpB,MAAM,QAAQ;;;;;;AAOd,IAAa,uBAAb,MAA6D;CAC3D,OAAgB;CAEhB;CACA;CAEA,YAAY,QAAyB;EACnC,IAAI,CAAC,OAAO,gBAAgB,MAAM,IAAI,MAAM,qDAAqD;EACjG,KAAK,QAAQ,IAAI,WAAW,OAAO,cAAc;CACnD;CAEA,MAAM,QAAQ,SAAyD;EACrE,MAAM,CAAC,UAAU,UAAU,MAAM,QAAQ,IAAI,CAAC,KAAK,MAAM,QAAQ,gBAAgB,GAAG,KAAK,MAAM,QAAQ,MAAM,CAAC,CAAC;EAC/G,IAAI,CAAC,YAAY,CAAC,QAChB,OAAO;GACL,QAAQ;GACR,iBAAiB;GACjB,YAAY;GACZ,KAAK;IAAE,UAAU;IAAS,OAAO;IAAO,WAAW;KAAE,UAAU,CAAC,CAAC;KAAU,QAAQ,CAAC,CAAC;IAAO;GAAE;EAChG;EAGF,MAAM,SAAS,SAAS,OAAO,QAAQ,KAAK,OAAO,MAAM,MAAM,QAAQ,OAAO,OAAO,IAAK,CAAC;EAE3F,OAAO;GACL,QAAQ,UAAU;GAClB,iBAAiB,QAAQ,MAAO,SAAS,eAAe,GAAG;GAC3D,YAAY,KAAK,IAAI,SAAS,WAAW,OAAO,SAAS;GACzD,KAAK;IAAE,UAAU;IAAS,OAAO;IAAO;GAAO;EACjD;CACF;CAEA,MAAM,MAAM,OAA4E;EACtF,IAAI,CAAC,MAAM,QAAQ,OAAO;EAE1B,MAAM,EAAE,UAAU,eAAe,MAAM,KAAK,OAAO;EACnD,MAAM,UAAU,MAAM,YAAY,KAAK;EACvC,MAAM,OAAO,QAAQ,MAAM,SAAS,OAAO,OAAO,CAAC;EACnD,IAAI,CAAC,MAAM,OAAO;EAElB,MAAM,UAAU,UAAU,SAAS,IAAI;EACvC,MAAM,SAAS,OAAO,OAAO,MAAM,WAAW,IAAI,SAAS,SAAS,KAAK,GAAG;GAAC;GAAG;GAAG;GAAc;EAAY,CAAC,CAAC,CAAC,CAAC;EACjH,MAAM,OAAO,KAAK,MAAM,GAAG,MAAM,KAAK;EAEtC,OAAO;GACL,QAAQ,MAAM,KAAK,SAAS,UAAU,QAAQ,IAAI;GAClD,OAAO;GACP,gBAAgB;GAChB,WAAW,KAAK;EAClB;CACF;CAEA,SAAiB;EACf,KAAK,YAAY,aAAa;GAC5B,UAAU,MAAM,aAAa,KAAK,KAAK,MAAM,KAAK,OAAO,CAAC;GAC1D,YAAY,MAAM,UAAU,KAAK,KAAK,MAAM,KAAK,OAAO,CAAC;EAC3D,GAAA,CAAI;EAEJ,OAAO,KAAK;CACd;AACF;;AAGA,SAAgB,QAAQ,OAA4C;CAClE,OAAO,MAAM,QACV,MAAM,SAAU,CAAC,QAAQ,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,OAAO,MACzF,IACF;AACF;AAEA,MAAM,WAAW,MAAsB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC"}
|
package/dist/drivers/mock.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FaceMatchDriver, FaceMatchRequest } from "../types.mjs";
|
|
1
|
+
import { FaceEmbedding, FaceMatchDriver, FaceMatchRequest } from "../types.mjs";
|
|
2
2
|
import { FaceMatchResultData } from "@arkyc/types";
|
|
3
3
|
|
|
4
4
|
//#region src/drivers/mock.d.ts
|
|
@@ -9,6 +9,8 @@ import { FaceMatchResultData } from "@arkyc/types";
|
|
|
9
9
|
declare class MockFaceMatchDriver implements FaceMatchDriver {
|
|
10
10
|
readonly name = "mock";
|
|
11
11
|
compare(request: FaceMatchRequest): Promise<FaceMatchResultData>;
|
|
12
|
+
/** A unit vector derived from the image bytes, so identical images embed identically. */
|
|
13
|
+
embed(image: Uint8Array): Promise<FaceEmbedding | null>;
|
|
12
14
|
}
|
|
13
15
|
//#endregion
|
|
14
16
|
export { MockFaceMatchDriver };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.d.mts","names":[],"sources":["../../src/drivers/mock.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"mock.d.mts","names":[],"sources":["../../src/drivers/mock.ts"],"mappings":";;;;;;AAYA;;cAAa,mBAAA,YAA+B,eAAA;EAAA,SACjC,IAAA;EAEH,OAAA,CAAQ,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,mBAAA;EAAR;EAapC,KAAA,CAAM,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,aAAA;AAAA"}
|
package/dist/drivers/mock.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
//#region src/drivers/mock.ts
|
|
3
|
+
const MOCK_EMBEDDING_SIZE = 128;
|
|
2
4
|
const clamp01 = (n) => Math.min(1, Math.max(0, n));
|
|
3
5
|
/**
|
|
4
6
|
* Deterministic face-match driver for development + tests. Passes by default;
|
|
@@ -18,6 +20,22 @@ var MockFaceMatchDriver = class {
|
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
22
|
}
|
|
23
|
+
/** A unit vector derived from the image bytes, so identical images embed identically. */
|
|
24
|
+
async embed(image) {
|
|
25
|
+
if (!image.length) return null;
|
|
26
|
+
const values = [];
|
|
27
|
+
for (let block = 0; values.length < MOCK_EMBEDDING_SIZE; block += 1) {
|
|
28
|
+
const digest = createHash("sha256").update(image).update(String(block)).digest();
|
|
29
|
+
for (const byte of digest) values.push(byte / 127.5 - 1);
|
|
30
|
+
}
|
|
31
|
+
const vector = values.slice(0, MOCK_EMBEDDING_SIZE);
|
|
32
|
+
const norm = Math.hypot(...vector) || 1;
|
|
33
|
+
return {
|
|
34
|
+
vector: vector.map((v) => v / norm),
|
|
35
|
+
model: "mock-v1",
|
|
36
|
+
matchThreshold: .95
|
|
37
|
+
};
|
|
38
|
+
}
|
|
21
39
|
};
|
|
22
40
|
//#endregion
|
|
23
41
|
export { MockFaceMatchDriver };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.mjs","names":[],"sources":["../../src/drivers/mock.ts"],"sourcesContent":["import type { FaceMatchResultData } from '@arkyc/types'\nimport type { FaceMatchDriver, FaceMatchRequest } from '../types'\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n))\n\n/**\n * Deterministic face-match driver for development + tests. Passes by default;\n * `hints` steer the similarity score and verdict.\n */\nexport class MockFaceMatchDriver implements FaceMatchDriver {\n readonly name = 'mock'\n\n async compare(request: FaceMatchRequest): Promise<FaceMatchResultData> {\n const similarityScore = clamp01(request.hints?.similarityScore ?? 0.9)\n const passed = request.hints?.passed ?? similarityScore >= 0.5\n\n return {\n passed,\n similarityScore,\n confidence: 0.93,\n raw: { provider: 'mock', similarityScore },\n }\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"mock.mjs","names":[],"sources":["../../src/drivers/mock.ts"],"sourcesContent":["import type { FaceMatchResultData } from '@arkyc/types'\nimport { createHash } from 'node:crypto'\nimport type { FaceEmbedding, FaceMatchDriver, FaceMatchRequest } from '../types'\n\nconst MOCK_EMBEDDING_SIZE = 128\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n))\n\n/**\n * Deterministic face-match driver for development + tests. Passes by default;\n * `hints` steer the similarity score and verdict.\n */\nexport class MockFaceMatchDriver implements FaceMatchDriver {\n readonly name = 'mock'\n\n async compare(request: FaceMatchRequest): Promise<FaceMatchResultData> {\n const similarityScore = clamp01(request.hints?.similarityScore ?? 0.9)\n const passed = request.hints?.passed ?? similarityScore >= 0.5\n\n return {\n passed,\n similarityScore,\n confidence: 0.93,\n raw: { provider: 'mock', similarityScore },\n }\n }\n\n /** A unit vector derived from the image bytes, so identical images embed identically. */\n async embed(image: Uint8Array): Promise<FaceEmbedding | null> {\n if (!image.length) return null\n\n const values: number[] = []\n for (let block = 0; values.length < MOCK_EMBEDDING_SIZE; block += 1) {\n const digest = createHash('sha256').update(image).update(String(block)).digest()\n for (const byte of digest) values.push(byte / 127.5 - 1)\n }\n const vector = values.slice(0, MOCK_EMBEDDING_SIZE)\n const norm = Math.hypot(...vector) || 1\n\n return { vector: vector.map((v) => v / norm), model: 'mock-v1', matchThreshold: 0.95 }\n }\n}\n"],"mappings":";;AAIA,MAAM,sBAAsB;AAE5B,MAAM,WAAW,MAAsB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;;;;;AAMjE,IAAa,sBAAb,MAA4D;CAC1D,OAAgB;CAEhB,MAAM,QAAQ,SAAyD;EACrE,MAAM,kBAAkB,QAAQ,QAAQ,OAAO,mBAAmB,EAAG;EAGrE,OAAO;GACL,QAHa,QAAQ,OAAO,UAAU,mBAAmB;GAIzD;GACA,YAAY;GACZ,KAAK;IAAE,UAAU;IAAQ;GAAgB;EAC3C;CACF;;CAGA,MAAM,MAAM,OAAkD;EAC5D,IAAI,CAAC,MAAM,QAAQ,OAAO;EAE1B,MAAM,SAAmB,CAAC;EAC1B,KAAK,IAAI,QAAQ,GAAG,OAAO,SAAS,qBAAqB,SAAS,GAAG;GACnE,MAAM,SAAS,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO;GAC/E,KAAK,MAAM,QAAQ,QAAQ,OAAO,KAAK,OAAO,QAAQ,CAAC;EACzD;EACA,MAAM,SAAS,OAAO,MAAM,GAAG,mBAAmB;EAClD,MAAM,OAAO,KAAK,MAAM,GAAG,MAAM,KAAK;EAEtC,OAAO;GAAE,QAAQ,OAAO,KAAK,MAAM,IAAI,IAAI;GAAG,OAAO;GAAW,gBAAgB;EAAK;CACvF;AACF"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { FaceMatchConfig, FaceMatchDriver, FaceMatchDriverName, FaceMatchRequest } from "./types.mjs";
|
|
1
|
+
import { FaceEmbedding, FaceMatchConfig, FaceMatchDriver, FaceMatchDriverName, FaceMatchRequest } from "./types.mjs";
|
|
2
2
|
import { FaceMatchDriverFactory } from "./registry.mjs";
|
|
3
3
|
import { MockFaceMatchDriver } from "./drivers/mock.mjs";
|
|
4
4
|
import { ExternalFaceMatchDriver } from "./drivers/external.mjs";
|
|
5
|
-
|
|
5
|
+
import { LocalFaceMatchDriver } from "./drivers/local.mjs";
|
|
6
|
+
export { ExternalFaceMatchDriver, FaceEmbedding, FaceMatchConfig, FaceMatchDriver, FaceMatchDriverFactory, FaceMatchDriverName, FaceMatchRequest, LocalFaceMatchDriver, MockFaceMatchDriver };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ExternalFaceMatchDriver } from "./drivers/external.mjs";
|
|
2
|
+
import { LocalFaceMatchDriver } from "./drivers/local.mjs";
|
|
2
3
|
import { MockFaceMatchDriver } from "./drivers/mock.mjs";
|
|
3
4
|
import { FaceMatchDriverFactory } from "./registry.mjs";
|
|
4
|
-
export { ExternalFaceMatchDriver, FaceMatchDriverFactory, MockFaceMatchDriver };
|
|
5
|
+
export { ExternalFaceMatchDriver, FaceMatchDriverFactory, LocalFaceMatchDriver, MockFaceMatchDriver };
|
package/dist/registry.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.mts","names":[],"sources":["../src/registry.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"registry.d.mts","names":[],"sources":["../src/registry.ts"],"mappings":";;;;cAOa,sBAAA;EAAA;;;;;;EAAA,OAOJ,MAAA,CAAO,MAAA,EAAQ,eAAA,GAAkB,eAAe;AAAA"}
|
package/dist/registry.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ExternalFaceMatchDriver } from "./drivers/external.mjs";
|
|
2
|
+
import { LocalFaceMatchDriver } from "./drivers/local.mjs";
|
|
2
3
|
import { MockFaceMatchDriver } from "./drivers/mock.mjs";
|
|
3
4
|
//#region src/registry.ts
|
|
4
5
|
/** Selects a face-match driver from config. */
|
|
@@ -13,6 +14,7 @@ var FaceMatchDriverFactory = class {
|
|
|
13
14
|
switch (config.driver) {
|
|
14
15
|
case "mock": return new MockFaceMatchDriver();
|
|
15
16
|
case "external": return new ExternalFaceMatchDriver(config);
|
|
17
|
+
case "local": return new LocalFaceMatchDriver(config);
|
|
16
18
|
default: throw new Error(`Unknown face-match driver: ${config.driver}`);
|
|
17
19
|
}
|
|
18
20
|
}
|
package/dist/registry.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.mjs","names":[],"sources":["../src/registry.ts"],"sourcesContent":["import type { FaceMatchConfig, FaceMatchDriver } from './types'\n\nimport { ExternalFaceMatchDriver } from './drivers/external'\nimport { MockFaceMatchDriver } from './drivers/mock'\n\n/** Selects a face-match driver from config. */\nexport class FaceMatchDriverFactory {\n /**'\n * Resolve the face-match driver named by `config`.\n *\n * @param config\n * @returns\n */\n static create(config: FaceMatchConfig): FaceMatchDriver {\n switch (config.driver) {\n case 'mock':\n return new MockFaceMatchDriver()\n case 'external':\n return new ExternalFaceMatchDriver(config)\n default:\n throw new Error(`Unknown face-match driver: ${(config as FaceMatchConfig).driver}`)\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"registry.mjs","names":[],"sources":["../src/registry.ts"],"sourcesContent":["import type { FaceMatchConfig, FaceMatchDriver } from './types'\n\nimport { ExternalFaceMatchDriver } from './drivers/external'\nimport { LocalFaceMatchDriver } from './drivers/local'\nimport { MockFaceMatchDriver } from './drivers/mock'\n\n/** Selects a face-match driver from config. */\nexport class FaceMatchDriverFactory {\n /**'\n * Resolve the face-match driver named by `config`.\n *\n * @param config\n * @returns\n */\n static create(config: FaceMatchConfig): FaceMatchDriver {\n switch (config.driver) {\n case 'mock':\n return new MockFaceMatchDriver()\n case 'external':\n return new ExternalFaceMatchDriver(config)\n case 'local':\n return new LocalFaceMatchDriver(config)\n default:\n throw new Error(`Unknown face-match driver: ${(config as FaceMatchConfig).driver}`)\n }\n }\n}\n"],"mappings":";;;;;AAOA,IAAa,yBAAb,MAAoC;;;;;;;CAOlC,OAAO,OAAO,QAA0C;EACtD,QAAQ,OAAO,QAAf;GACE,KAAK,QACH,OAAO,IAAI,oBAAoB;GACjC,KAAK,YACH,OAAO,IAAI,wBAAwB,MAAM;GAC3C,KAAK,SACH,OAAO,IAAI,qBAAqB,MAAM;GACxC,SACE,MAAM,IAAI,MAAM,8BAA+B,OAA2B,QAAQ;EACtF;CACF;AACF"}
|
package/dist/types.d.mts
CHANGED
|
@@ -16,19 +16,31 @@ interface FaceMatchRequest {
|
|
|
16
16
|
passed?: boolean;
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/** A face embedding: a vector two images of the same person land close together in. */
|
|
20
|
+
interface FaceEmbedding {
|
|
21
|
+
vector: number[];
|
|
22
|
+
/** The model that produced it. Embeddings from different models are not comparable. */
|
|
23
|
+
model: string;
|
|
24
|
+
/** Cosine similarity at or above which two embeddings are the same person. */
|
|
25
|
+
matchThreshold: number;
|
|
26
|
+
}
|
|
19
27
|
/** A pluggable face-match provider. */
|
|
20
28
|
interface FaceMatchDriver {
|
|
21
29
|
readonly name: string;
|
|
22
30
|
compare(request: FaceMatchRequest): Promise<FaceMatchResultData>;
|
|
31
|
+
/** Embed the most prominent face in an image, or `null` when there is none. Optional capability. */
|
|
32
|
+
embed?(image: Uint8Array): Promise<FaceEmbedding | null>;
|
|
23
33
|
}
|
|
24
34
|
/** Identifier for a registered face-match driver. */
|
|
25
|
-
type FaceMatchDriverName = 'mock' | 'external';
|
|
35
|
+
type FaceMatchDriverName = 'mock' | 'external' | 'local';
|
|
26
36
|
/** Configuration selecting + parameterising the active face-match driver. */
|
|
27
37
|
interface FaceMatchConfig {
|
|
28
38
|
driver: FaceMatchDriverName;
|
|
29
39
|
endpoint?: string;
|
|
30
40
|
apiKey?: string;
|
|
41
|
+
/** Where the `local` driver finds its model files. */
|
|
42
|
+
modelDirectory?: string;
|
|
31
43
|
}
|
|
32
44
|
//#endregion
|
|
33
|
-
export { FaceMatchConfig, FaceMatchDriver, FaceMatchDriverName, FaceMatchRequest };
|
|
45
|
+
export { FaceEmbedding, FaceMatchConfig, FaceMatchDriver, FaceMatchDriverName, FaceMatchRequest };
|
|
34
46
|
//# sourceMappingURL=types.d.mts.map
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;UAGiB,gBAAA;EAAA;EAEf,gBAAA,EAAkB,UAAA;;EAElB,MAAA,EAAQ,UAAU;EAFlB;;;;EAOA,KAAA;IAAU,eAAA;IAA0B,MAAA;EAAA;AAAA;AAItC;AAAA,UAAiB,eAAA;EAAA,SACN,IAAA;EACT,OAAA,CAAQ,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,mBAAA;AAAA;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;UAGiB,gBAAA;EAAA;EAEf,gBAAA,EAAkB,UAAA;;EAElB,MAAA,EAAQ,UAAU;EAFlB;;;;EAOA,KAAA;IAAU,eAAA;IAA0B,MAAA;EAAA;AAAA;AAItC;AAAA,UAAiB,aAAA;EACf,MAAA;EAD4B;EAG5B,KAAA;EAAA;EAEA,cAAA;AAAA;AAAc;AAAA,UAIC,eAAA;EAAA,SACN,IAAA;EACT,OAAA,CAAQ,OAAA,EAAS,gBAAA,GAAmB,OAAA,CAAQ,mBAAA;EAA3B;EAEjB,KAAA,EAAO,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,aAAA;AAAA;;KAIzB,mBAAA;;UAGK,eAAA;EACf,MAAA,EAAQ,mBAAmB;EAC3B,QAAA;EACA,MAAA;EAZiB;EAcjB,cAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkyc/face-match",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Driver-based face matching",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@arkyc/types": "^
|
|
39
|
+
"@arkyc/types": "^2.0.0",
|
|
40
|
+
"@arkyc/vision": "^2.0.0"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"typecheck": "tsc --noEmit",
|