@srsergio/taptapp-ar 1.0.43 → 1.0.50
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 +42 -45
- package/dist/compiler/aframe.js +8 -8
- package/dist/compiler/controller.d.ts +50 -76
- package/dist/compiler/controller.js +72 -116
- package/dist/compiler/detector/detector-lite.js +82 -99
- package/dist/compiler/index.js +3 -3
- package/dist/compiler/matching/hamming-distance.d.ts +8 -0
- package/dist/compiler/matching/hamming-distance.js +35 -16
- package/dist/compiler/matching/hierarchical-clustering.d.ts +9 -0
- package/dist/compiler/matching/hierarchical-clustering.js +76 -56
- package/dist/compiler/matching/matching.js +3 -3
- package/dist/compiler/node-worker.js +144 -18
- package/dist/compiler/offline-compiler.d.ts +34 -83
- package/dist/compiler/offline-compiler.js +92 -96
- package/dist/compiler/simple-ar.d.ts +31 -57
- package/dist/compiler/simple-ar.js +32 -73
- package/dist/compiler/three.d.ts +13 -8
- package/dist/compiler/three.js +6 -6
- package/dist/compiler/tracker/extract.js +17 -14
- package/dist/compiler/utils/images.js +11 -16
- package/dist/compiler/utils/lsh-direct.d.ts +12 -0
- package/dist/compiler/utils/lsh-direct.js +76 -0
- package/dist/compiler/utils/worker-pool.js +10 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.js +1 -1
- package/package.json +2 -1
- package/src/compiler/aframe.js +8 -8
- package/src/compiler/controller.ts +512 -0
- package/src/compiler/detector/detector-lite.js +87 -107
- package/src/compiler/index.js +3 -3
- package/src/compiler/matching/hamming-distance.js +39 -16
- package/src/compiler/matching/hierarchical-clustering.js +85 -57
- package/src/compiler/matching/matching.js +3 -3
- package/src/compiler/node-worker.js +163 -18
- package/src/compiler/offline-compiler.ts +513 -0
- package/src/compiler/{simple-ar.js → simple-ar.ts} +64 -91
- package/src/compiler/three.js +6 -6
- package/src/compiler/tracker/extract.js +18 -15
- package/src/compiler/utils/images.js +11 -21
- package/src/compiler/utils/lsh-direct.js +86 -0
- package/src/compiler/utils/worker-pool.js +9 -1
- package/src/index.ts +2 -2
- package/src/react/types.ts +2 -2
- package/src/compiler/controller.js +0 -554
- package/src/compiler/offline-compiler.js +0 -515
|
@@ -1 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic compute for backward compatibility
|
|
3
|
+
*/
|
|
1
4
|
export function compute(options: any): number;
|
|
5
|
+
/**
|
|
6
|
+
* Super-optimized Hamming distance for 64-bit LSH (2x Uint32)
|
|
7
|
+
* NO OBJECTS, NO OPTIONS, JUST PURE SPEED.
|
|
8
|
+
*/
|
|
9
|
+
export function compute64(v1: any, v1Idx: any, v2: any, v2Idx: any): number;
|
|
@@ -9,39 +9,58 @@ for (let i = 0; i < 256; i++) {
|
|
|
9
9
|
BIT_COUNT_8[i] = c;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* 🚀 Moonshot Optimized Popcount
|
|
13
|
+
* Uses a slightly faster bitwise sequence for 32-bit integers
|
|
13
14
|
*/
|
|
14
15
|
function popcount32(n) {
|
|
15
|
-
n = n
|
|
16
|
-
n
|
|
17
|
-
|
|
16
|
+
n = n >>> 0; // Force unsigned
|
|
17
|
+
n -= (n >>> 1) & 0x55555555;
|
|
18
|
+
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
|
|
19
|
+
return (((n + (n >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Super-optimized Hamming distance for 64-bit LSH (2x Uint32)
|
|
23
|
+
* NO OBJECTS, NO OPTIONS, JUST PURE SPEED.
|
|
24
|
+
*/
|
|
25
|
+
const compute64 = (v1, v1Idx, v2, v2Idx) => {
|
|
26
|
+
// Inline XOR and popcount for maximum speed
|
|
27
|
+
let x1 = (v1[v1Idx] ^ v2[v2Idx]) >>> 0;
|
|
28
|
+
let x2 = (v1[v1Idx + 1] ^ v2[v2Idx + 1]) >>> 0;
|
|
29
|
+
// Popcount 1
|
|
30
|
+
x1 -= (x1 >>> 1) & 0x55555555;
|
|
31
|
+
x1 = (x1 & 0x33333333) + ((x1 >>> 2) & 0x33333333);
|
|
32
|
+
const count1 = (((x1 + (x1 >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
33
|
+
// Popcount 2
|
|
34
|
+
x2 -= (x2 >>> 1) & 0x55555555;
|
|
35
|
+
x2 = (x2 & 0x33333333) + ((x2 >>> 2) & 0x33333333);
|
|
36
|
+
const count2 = (((x2 + (x2 >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
|
|
37
|
+
return count1 + count2;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Generic compute for backward compatibility
|
|
41
|
+
*/
|
|
19
42
|
const compute = (options) => {
|
|
20
43
|
const { v1, v2, v1Offset = 0, v2Offset = 0 } = options;
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
// Assuming if it's not 84 bytes, it's the new 8-byte format.
|
|
44
|
+
const v2Len = v2.length - v2Offset;
|
|
45
|
+
if (v2Len === 2) {
|
|
46
|
+
return compute64(v1, v1Offset, v2, v2Offset);
|
|
25
47
|
}
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
48
|
+
// Protocol V4: 84-byte descriptors (Uint8Array)
|
|
49
|
+
if (v2Len === 84) {
|
|
28
50
|
let d = 0;
|
|
29
51
|
for (let i = 0; i < 84; i++) {
|
|
30
52
|
d += BIT_COUNT_8[v1[v1Offset + i] ^ v2[v2Offset + i]];
|
|
31
53
|
}
|
|
32
54
|
return d;
|
|
33
55
|
}
|
|
34
|
-
// Protocol V5.1
|
|
35
|
-
|
|
36
|
-
if (v1.length >= v1Offset + 4 && v2.length >= v2Offset + 4 && v1[v1Offset + 3] !== undefined) {
|
|
56
|
+
// Protocol V5.1: 128-bit LSH (4 x Uint32)
|
|
57
|
+
if (v2Len === 4) {
|
|
37
58
|
return popcount32(v1[v1Offset] ^ v2[v2Offset]) +
|
|
38
59
|
popcount32(v1[v1Offset + 1] ^ v2[v2Offset + 1]) +
|
|
39
60
|
popcount32(v1[v1Offset + 2] ^ v2[v2Offset + 2]) +
|
|
40
61
|
popcount32(v1[v1Offset + 3] ^ v2[v2Offset + 3]);
|
|
41
62
|
}
|
|
42
|
-
// Protocol V5 Path: LSH 64-bit (2 x 32-bit)
|
|
43
|
-
// We expect v1 and v2 to be slices or offsets of Uint32Array
|
|
44
63
|
return popcount32(v1[v1Offset] ^ v2[v2Offset]) +
|
|
45
64
|
popcount32(v1[v1Offset + 1] ^ v2[v2Offset + 1]);
|
|
46
65
|
};
|
|
47
|
-
export { compute };
|
|
66
|
+
export { compute, compute64 };
|
|
@@ -1,109 +1,129 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compute64 as hammingCompute64 } from "./hamming-distance.js";
|
|
2
2
|
import { createRandomizer } from "../utils/randomizer.js";
|
|
3
|
-
const MIN_FEATURE_PER_NODE = 16
|
|
4
|
-
const NUM_ASSIGNMENT_HYPOTHESES =
|
|
3
|
+
const MIN_FEATURE_PER_NODE = 32; // Increased from 16 for speed
|
|
4
|
+
const NUM_ASSIGNMENT_HYPOTHESES = 12; // Reduced from 16 for speed
|
|
5
5
|
const NUM_CENTERS = 8;
|
|
6
|
+
/**
|
|
7
|
+
* 🚀 Moonshot Optimized K-Medoids
|
|
8
|
+
*
|
|
9
|
+
* Major Optimizations:
|
|
10
|
+
* 1. Flattened Memory: Operates on a single Uint32Array block instead of objects.
|
|
11
|
+
* 2. Zero Property Access: Avoids .descriptors lookup in the tightest loop.
|
|
12
|
+
* 3. Cache-Friendly: Accesses contiguous descriptor data.
|
|
13
|
+
*/
|
|
6
14
|
const _computeKMedoids = (options) => {
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
const { descriptors, pointIndexes, randomizer } = options;
|
|
16
|
+
const numPointIndexes = pointIndexes.length;
|
|
17
|
+
const randomPointIndexes = new Int32Array(numPointIndexes);
|
|
18
|
+
for (let i = 0; i < numPointIndexes; i++) {
|
|
19
|
+
randomPointIndexes[i] = i;
|
|
11
20
|
}
|
|
12
21
|
let bestSumD = Number.MAX_SAFE_INTEGER;
|
|
13
|
-
let
|
|
14
|
-
|
|
22
|
+
let bestAssignment = null;
|
|
23
|
+
// Pre-fetch centers indices to avoid nested index lookups
|
|
24
|
+
const centerPointIndices = new Int32Array(NUM_CENTERS);
|
|
15
25
|
for (let i = 0; i < NUM_ASSIGNMENT_HYPOTHESES; i++) {
|
|
16
26
|
randomizer.arrayShuffle({ arr: randomPointIndexes, sampleSize: NUM_CENTERS });
|
|
27
|
+
// Set centers for this hypothesis
|
|
28
|
+
for (let k = 0; k < NUM_CENTERS; k++) {
|
|
29
|
+
centerPointIndices[k] = pointIndexes[randomPointIndexes[k]];
|
|
30
|
+
}
|
|
17
31
|
let sumD = 0;
|
|
18
|
-
const
|
|
19
|
-
for (let j = 0; j <
|
|
20
|
-
|
|
32
|
+
const currentAssignment = new Int32Array(numPointIndexes);
|
|
33
|
+
for (let j = 0; j < numPointIndexes; j++) {
|
|
34
|
+
const pIdx = pointIndexes[j];
|
|
35
|
+
const pOffset = pIdx * 2;
|
|
36
|
+
let bestD = 255; // Max possible Hamming for 64-bit is 64, but let's be safe
|
|
37
|
+
let bestCenterIdx = -1;
|
|
21
38
|
for (let k = 0; k < NUM_CENTERS; k++) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
39
|
+
const cIdx = centerPointIndices[k];
|
|
40
|
+
const cOffset = cIdx * 2;
|
|
41
|
+
// DIRECT CALL TO INLINED HAMMING
|
|
42
|
+
const d = hammingCompute64(descriptors, pOffset, descriptors, cOffset);
|
|
27
43
|
if (d < bestD) {
|
|
28
|
-
|
|
44
|
+
bestCenterIdx = randomPointIndexes[k];
|
|
29
45
|
bestD = d;
|
|
30
46
|
}
|
|
31
47
|
}
|
|
48
|
+
currentAssignment[j] = bestCenterIdx;
|
|
32
49
|
sumD += bestD;
|
|
33
50
|
}
|
|
34
|
-
assignments.push(assignment);
|
|
35
51
|
if (sumD < bestSumD) {
|
|
36
52
|
bestSumD = sumD;
|
|
37
|
-
|
|
53
|
+
bestAssignment = currentAssignment;
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
|
-
return
|
|
56
|
+
return bestAssignment;
|
|
41
57
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// isLeaf: bool,
|
|
46
|
-
// children: [], list of children node
|
|
47
|
-
// pointIndexes: [], list of int, point indexes
|
|
48
|
-
// centerPointIndex: int
|
|
49
|
-
// }
|
|
58
|
+
/**
|
|
59
|
+
* Build hierarchical clusters
|
|
60
|
+
*/
|
|
50
61
|
const build = ({ points }) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
pointIndexes
|
|
62
|
+
const numPoints = points.length;
|
|
63
|
+
if (numPoints === 0)
|
|
64
|
+
return { rootNode: { leaf: true, pointIndexes: [], centerPointIndex: null } };
|
|
65
|
+
// 🚀 MOONSHOT: Flatten all descriptors into a single Uint32Array
|
|
66
|
+
// This is the key to sub-second performance.
|
|
67
|
+
const descriptors = new Uint32Array(numPoints * 2);
|
|
68
|
+
for (let i = 0; i < numPoints; i++) {
|
|
69
|
+
const d = points[i].descriptors;
|
|
70
|
+
descriptors[i * 2] = d[0];
|
|
71
|
+
descriptors[i * 2 + 1] = d[1];
|
|
72
|
+
}
|
|
73
|
+
const pointIndexes = new Int32Array(numPoints);
|
|
74
|
+
for (let i = 0; i < numPoints; i++) {
|
|
75
|
+
pointIndexes[i] = i;
|
|
54
76
|
}
|
|
55
77
|
const randomizer = createRandomizer();
|
|
56
78
|
const rootNode = _build({
|
|
57
|
-
|
|
58
|
-
pointIndexes
|
|
79
|
+
descriptors,
|
|
80
|
+
pointIndexes,
|
|
59
81
|
centerPointIndex: null,
|
|
60
82
|
randomizer,
|
|
61
83
|
});
|
|
62
84
|
return { rootNode };
|
|
63
85
|
};
|
|
64
|
-
// recursive build hierarchy clusters
|
|
65
86
|
const _build = (options) => {
|
|
66
|
-
const {
|
|
87
|
+
const { descriptors, pointIndexes, centerPointIndex, randomizer } = options;
|
|
88
|
+
const numPoints = pointIndexes.length;
|
|
67
89
|
let isLeaf = false;
|
|
68
|
-
if (
|
|
90
|
+
if (numPoints <= NUM_CENTERS || numPoints <= MIN_FEATURE_PER_NODE) {
|
|
69
91
|
isLeaf = true;
|
|
70
92
|
}
|
|
71
|
-
const clusters =
|
|
93
|
+
const clusters = new Map();
|
|
72
94
|
if (!isLeaf) {
|
|
73
|
-
|
|
74
|
-
const assignment = _computeKMedoids({ points, pointIndexes, randomizer });
|
|
95
|
+
const assignment = _computeKMedoids({ descriptors, pointIndexes, randomizer });
|
|
75
96
|
for (let i = 0; i < assignment.length; i++) {
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
const centerIdx = pointIndexes[assignment[i]];
|
|
98
|
+
let cluster = clusters.get(centerIdx);
|
|
99
|
+
if (cluster === undefined) {
|
|
100
|
+
cluster = [];
|
|
101
|
+
clusters.set(centerIdx, cluster);
|
|
78
102
|
}
|
|
79
|
-
|
|
103
|
+
cluster.push(pointIndexes[i]);
|
|
104
|
+
}
|
|
105
|
+
if (clusters.size === 1) {
|
|
106
|
+
isLeaf = true;
|
|
80
107
|
}
|
|
81
|
-
}
|
|
82
|
-
if (Object.keys(clusters).length === 1) {
|
|
83
|
-
isLeaf = true;
|
|
84
108
|
}
|
|
85
109
|
const node = {
|
|
86
110
|
centerPointIndex: centerPointIndex,
|
|
87
111
|
};
|
|
88
112
|
if (isLeaf) {
|
|
89
113
|
node.leaf = true;
|
|
90
|
-
node.pointIndexes =
|
|
91
|
-
for (let i = 0; i < pointIndexes.length; i++) {
|
|
92
|
-
node.pointIndexes.push(pointIndexes[i]);
|
|
93
|
-
}
|
|
114
|
+
node.pointIndexes = new Int32Array(pointIndexes);
|
|
94
115
|
return node;
|
|
95
116
|
}
|
|
96
|
-
// recursive build children
|
|
97
117
|
node.leaf = false;
|
|
98
118
|
node.children = [];
|
|
99
|
-
|
|
119
|
+
for (const [cIdx, clusterPoints] of clusters) {
|
|
100
120
|
node.children.push(_build({
|
|
101
|
-
|
|
102
|
-
pointIndexes:
|
|
103
|
-
centerPointIndex:
|
|
121
|
+
descriptors,
|
|
122
|
+
pointIndexes: new Int32Array(clusterPoints),
|
|
123
|
+
centerPointIndex: cIdx,
|
|
104
124
|
randomizer,
|
|
105
125
|
}));
|
|
106
|
-
}
|
|
126
|
+
}
|
|
107
127
|
return node;
|
|
108
128
|
};
|
|
109
129
|
export { build };
|
|
@@ -3,11 +3,11 @@ import { compute as hammingCompute } from "./hamming-distance.js";
|
|
|
3
3
|
import { computeHoughMatches } from "./hough.js";
|
|
4
4
|
import { computeHomography } from "./ransacHomography.js";
|
|
5
5
|
import { multiplyPointHomographyInhomogenous, matrixInverse33 } from "../utils/geometry.js";
|
|
6
|
-
const INLIER_THRESHOLD = 3
|
|
6
|
+
const INLIER_THRESHOLD = 10; // Relaxed from 3 to 10 for better robustness with LSH
|
|
7
7
|
//const MIN_NUM_INLIERS = 8; //default
|
|
8
8
|
const MIN_NUM_INLIERS = 6;
|
|
9
|
-
const CLUSTER_MAX_POP =
|
|
10
|
-
const HAMMING_THRESHOLD = 0.
|
|
9
|
+
const CLUSTER_MAX_POP = 20; // Increased to explore more candidate clusters
|
|
10
|
+
const HAMMING_THRESHOLD = 0.85; // Relaxed ratio test for binary descriptors
|
|
11
11
|
// match list of querpoints against pre-built list of keyframes
|
|
12
12
|
const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) => {
|
|
13
13
|
let debugExtra = {};
|
|
@@ -6,12 +6,35 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { parentPort } from 'node:worker_threads';
|
|
8
8
|
import { extractTrackingFeatures } from './tracker/extract-utils.js';
|
|
9
|
-
import { buildTrackingImageList
|
|
9
|
+
import { buildTrackingImageList } from './image-list.js';
|
|
10
10
|
import { DetectorLite } from './detector/detector-lite.js';
|
|
11
11
|
import { build as hierarchicalClusteringBuild } from './matching/hierarchical-clustering.js';
|
|
12
12
|
if (!parentPort) {
|
|
13
13
|
throw new Error('This file must be run as a worker thread.');
|
|
14
14
|
}
|
|
15
|
+
// Helper for Morton Order sorting inside worker
|
|
16
|
+
function getMorton(x, y) {
|
|
17
|
+
let x_int = x | 0;
|
|
18
|
+
let y_int = y | 0;
|
|
19
|
+
x_int = (x_int | (x_int << 8)) & 0x00FF00FF;
|
|
20
|
+
x_int = (x_int | (x_int << 4)) & 0x0F0F0F0F;
|
|
21
|
+
x_int = (x_int | (x_int << 2)) & 0x33333333;
|
|
22
|
+
x_int = (x_int | (x_int << 1)) & 0x55555555;
|
|
23
|
+
y_int = (y_int | (y_int << 8)) & 0x00FF00FF;
|
|
24
|
+
y_int = (y_int | (y_int << 4)) & 0x0F0F0F0F;
|
|
25
|
+
y_int = (y_int | (y_int << 2)) & 0x33333333;
|
|
26
|
+
y_int = (y_int | (y_int << 1)) & 0x55555555;
|
|
27
|
+
return x_int | (y_int << 1);
|
|
28
|
+
}
|
|
29
|
+
const mortonCache = new Int32Array(2048); // Cache for sorting stability
|
|
30
|
+
function sortPoints(points) {
|
|
31
|
+
if (points.length <= 1)
|
|
32
|
+
return points;
|
|
33
|
+
// Sort in-place to avoid allocations
|
|
34
|
+
return points.sort((a, b) => {
|
|
35
|
+
return getMorton(a.x, a.y) - getMorton(b.x, b.y);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
15
38
|
parentPort.on('message', async (msg) => {
|
|
16
39
|
if (msg.type === 'compile') {
|
|
17
40
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
@@ -34,24 +57,50 @@ parentPort.on('message', async (msg) => {
|
|
|
34
57
|
catch (error) {
|
|
35
58
|
parentPort.postMessage({
|
|
36
59
|
type: 'error',
|
|
37
|
-
error: error.message
|
|
60
|
+
error: error.message + '\n' + error.stack
|
|
38
61
|
});
|
|
39
62
|
}
|
|
40
63
|
}
|
|
41
64
|
else if (msg.type === 'match') {
|
|
42
65
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
43
66
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
// 🚀 MOONSHOT: Only run detector ONCE on full-res image.
|
|
68
|
+
// DetectorLite internally builds a pyramid (octaves 1.0, 0.5, 0.25, etc.)
|
|
69
|
+
const detector = new DetectorLite(targetImage.width, targetImage.height, {
|
|
70
|
+
useLSH: true
|
|
71
|
+
});
|
|
72
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.1 });
|
|
73
|
+
const { featurePoints: allPoints } = detector.detect(targetImage.data);
|
|
74
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.5 });
|
|
75
|
+
// Group points by their scale (octave)
|
|
76
|
+
const scalesMap = new Map();
|
|
77
|
+
for (const p of allPoints) {
|
|
78
|
+
const octaveScale = p.scale;
|
|
79
|
+
let list = scalesMap.get(octaveScale);
|
|
80
|
+
if (!list) {
|
|
81
|
+
list = [];
|
|
82
|
+
scalesMap.set(octaveScale, list);
|
|
83
|
+
}
|
|
84
|
+
// Coordinates in p are already full-res.
|
|
85
|
+
// We need them relative to the scaled image for the keyframe.
|
|
86
|
+
list.push({
|
|
87
|
+
...p,
|
|
88
|
+
x: p.x / octaveScale,
|
|
89
|
+
y: p.y / octaveScale,
|
|
90
|
+
scale: 1.0 // Keypoint scale is always 1.0 relative to its own keyframe image
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Optional: Run another detector pass at an intermediate scale to improve coverage
|
|
94
|
+
// (e.g. at 1/1.41 ratio) if tracking robustness suffers.
|
|
95
|
+
// For now, let's stick to octaves for MAXIMUM speed.
|
|
47
96
|
const keyframes = [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const maximaPoints =
|
|
54
|
-
const minimaPoints =
|
|
97
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
98
|
+
const percentPerScale = (percentPerImage * 0.4) / sortedScales.length;
|
|
99
|
+
for (const s of sortedScales) {
|
|
100
|
+
const ps = scalesMap.get(s);
|
|
101
|
+
const sortedPs = sortPoints(ps);
|
|
102
|
+
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
103
|
+
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
55
104
|
const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
|
|
56
105
|
const minimaPointsCluster = hierarchicalClusteringBuild({ points: minimaPoints });
|
|
57
106
|
keyframes.push({
|
|
@@ -59,14 +108,13 @@ parentPort.on('message', async (msg) => {
|
|
|
59
108
|
minimaPoints,
|
|
60
109
|
maximaPointsCluster,
|
|
61
110
|
minimaPointsCluster,
|
|
62
|
-
width:
|
|
63
|
-
height:
|
|
64
|
-
scale:
|
|
111
|
+
width: Math.round(targetImage.width / s),
|
|
112
|
+
height: Math.round(targetImage.height / s),
|
|
113
|
+
scale: 1.0 / s, // keyframe.scale is relative to full target image
|
|
65
114
|
});
|
|
66
|
-
localPercent += percentPerAction;
|
|
67
115
|
parentPort.postMessage({
|
|
68
116
|
type: 'progress',
|
|
69
|
-
percent: basePercent +
|
|
117
|
+
percent: basePercent + percentPerImage * 0.6 + keyframes.length * percentPerScale
|
|
70
118
|
});
|
|
71
119
|
}
|
|
72
120
|
parentPort.postMessage({
|
|
@@ -77,7 +125,85 @@ parentPort.on('message', async (msg) => {
|
|
|
77
125
|
catch (error) {
|
|
78
126
|
parentPort.postMessage({
|
|
79
127
|
type: 'error',
|
|
80
|
-
error: error.message
|
|
128
|
+
error: error.message + '\n' + error.stack
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (msg.type === 'compile-all') {
|
|
133
|
+
const { targetImage } = msg;
|
|
134
|
+
try {
|
|
135
|
+
// 1. Single Pass Detection + Pyramid Generation
|
|
136
|
+
const detector = new DetectorLite(targetImage.width, targetImage.height, { useLSH: true });
|
|
137
|
+
parentPort.postMessage({ type: 'progress', percent: 10 });
|
|
138
|
+
const { featurePoints, pyramid } = detector.detect(targetImage.data);
|
|
139
|
+
parentPort.postMessage({ type: 'progress', percent: 40 });
|
|
140
|
+
// 2. Extract Tracking Data using the ALREADY BLURRED pyramid
|
|
141
|
+
// We need 2 levels closest to 256 and 128
|
|
142
|
+
const trackingImageList = [];
|
|
143
|
+
// Octave 0 is Original blured. Octave 1 is 0.5x. Octave 2 is 0.25x.
|
|
144
|
+
// We'll pick the best ones.
|
|
145
|
+
const targetSizes = [256, 128];
|
|
146
|
+
for (const targetSize of targetSizes) {
|
|
147
|
+
let bestLevel = 0;
|
|
148
|
+
let minDiff = Math.abs(Math.min(targetImage.width, targetImage.height) - targetSize);
|
|
149
|
+
for (let l = 1; l < pyramid.length; l++) {
|
|
150
|
+
const img = pyramid[l][0];
|
|
151
|
+
const diff = Math.abs(Math.min(img.width, img.height) - targetSize);
|
|
152
|
+
if (diff < minDiff) {
|
|
153
|
+
minDiff = diff;
|
|
154
|
+
bestLevel = l;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const levelImg = pyramid[bestLevel][0];
|
|
158
|
+
trackingImageList.push({
|
|
159
|
+
data: levelImg.data,
|
|
160
|
+
width: levelImg.width,
|
|
161
|
+
height: levelImg.height,
|
|
162
|
+
scale: levelImg.width / targetImage.width
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const trackingData = extractTrackingFeatures(trackingImageList, () => { });
|
|
166
|
+
parentPort.postMessage({ type: 'progress', percent: 60 });
|
|
167
|
+
// 3. Build Keyframes for Matching
|
|
168
|
+
const scalesMap = new Map();
|
|
169
|
+
for (const p of featurePoints) {
|
|
170
|
+
const s = p.scale;
|
|
171
|
+
let list = scalesMap.get(s);
|
|
172
|
+
if (!list) {
|
|
173
|
+
list = [];
|
|
174
|
+
scalesMap.set(s, list);
|
|
175
|
+
}
|
|
176
|
+
list.push({ ...p, x: p.x / s, y: p.y / s, scale: 1.0 });
|
|
177
|
+
}
|
|
178
|
+
const keyframes = [];
|
|
179
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
180
|
+
for (const s of sortedScales) {
|
|
181
|
+
const ps = scalesMap.get(s);
|
|
182
|
+
const sortedPs = sortPoints(ps);
|
|
183
|
+
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
184
|
+
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
185
|
+
const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
|
|
186
|
+
const minimaPointsCluster = hierarchicalClusteringBuild({ points: minimaPoints });
|
|
187
|
+
keyframes.push({
|
|
188
|
+
maximaPoints,
|
|
189
|
+
minimaPoints,
|
|
190
|
+
maximaPointsCluster,
|
|
191
|
+
minimaPointsCluster,
|
|
192
|
+
width: Math.round(targetImage.width / s),
|
|
193
|
+
height: Math.round(targetImage.height / s),
|
|
194
|
+
scale: 1.0 / s,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
parentPort.postMessage({
|
|
198
|
+
type: 'compileDone', // Reusing message type for compatibility with WorkerPool
|
|
199
|
+
matchingData: keyframes,
|
|
200
|
+
trackingData: trackingData
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
parentPort.postMessage({
|
|
205
|
+
type: 'error',
|
|
206
|
+
error: error.message + '\n' + error.stack
|
|
81
207
|
});
|
|
82
208
|
}
|
|
83
209
|
}
|
|
@@ -1,62 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compilador
|
|
2
|
+
* @fileoverview Compilador Offline Optimizado - Sin TensorFlow para máxima velocidad
|
|
3
|
+
*
|
|
4
|
+
* Este módulo implementa un compilador de imágenes AR ultrarrápido
|
|
5
|
+
* que NO depende de TensorFlow, eliminando todos los problemas de
|
|
6
|
+
* inicialización, bloqueos y compatibilidad.
|
|
3
7
|
*/
|
|
4
|
-
|
|
8
|
+
import { WorkerPool } from "./utils/worker-pool.js";
|
|
9
|
+
export declare class OfflineCompiler {
|
|
5
10
|
data: any;
|
|
6
11
|
workerPool: WorkerPool | null;
|
|
12
|
+
constructor();
|
|
7
13
|
_initNodeWorkers(): Promise<void>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @returns {Promise<Array>} Datos compilados
|
|
13
|
-
*/
|
|
14
|
-
compileImageTargets(images: any[], progressCallback: Function): Promise<any[]>;
|
|
15
|
-
_compileMatch(targetImages: any, progressCallback: any): Promise<any[]>;
|
|
16
|
-
_compileTrack(targetImages: any, progressCallback: any): Promise<any[]>;
|
|
14
|
+
compileImageTargets(images: any[], progressCallback: (p: number) => void): Promise<any>;
|
|
15
|
+
_compileTarget(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
16
|
+
_compileMatch(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
17
|
+
_compileTrack(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
17
18
|
compileTrack({ progressCallback, targetImages, basePercent }: {
|
|
18
|
-
progressCallback:
|
|
19
|
-
targetImages: any;
|
|
20
|
-
basePercent?: number
|
|
19
|
+
progressCallback: (p: number) => void;
|
|
20
|
+
targetImages: any[];
|
|
21
|
+
basePercent?: number;
|
|
21
22
|
}): Promise<any[]>;
|
|
22
23
|
compileMatch({ progressCallback, targetImages, basePercent }: {
|
|
23
|
-
progressCallback:
|
|
24
|
-
targetImages: any;
|
|
25
|
-
basePercent?: number
|
|
24
|
+
progressCallback: (p: number) => void;
|
|
25
|
+
targetImages: any[];
|
|
26
|
+
basePercent?: number;
|
|
26
27
|
}): Promise<any[]>;
|
|
27
|
-
exportData():
|
|
28
|
-
_getMorton(x:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
x: Uint16Array<any>;
|
|
35
|
-
y: Uint16Array<any>;
|
|
36
|
-
a: Int16Array<any>;
|
|
37
|
-
s: Uint8Array<any>;
|
|
38
|
-
d: Uint32Array<ArrayBuffer>;
|
|
39
|
-
t: any[];
|
|
40
|
-
};
|
|
41
|
-
min: {
|
|
42
|
-
x: Uint16Array<any>;
|
|
43
|
-
y: Uint16Array<any>;
|
|
44
|
-
a: Int16Array<any>;
|
|
45
|
-
s: Uint8Array<any>;
|
|
46
|
-
d: Uint32Array<ArrayBuffer>;
|
|
47
|
-
t: any[];
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
_columnarize(points: any, tree: any, width: any, height: any): {
|
|
51
|
-
x: Uint16Array<any>;
|
|
52
|
-
y: Uint16Array<any>;
|
|
53
|
-
a: Int16Array<any>;
|
|
54
|
-
s: Uint8Array<any>;
|
|
28
|
+
exportData(): Uint8Array<ArrayBuffer>;
|
|
29
|
+
_getMorton(x: number, y: number): number;
|
|
30
|
+
_columnarize(points: any[], tree: any, width: number, height: number): {
|
|
31
|
+
x: Uint16Array<ArrayBuffer>;
|
|
32
|
+
y: Uint16Array<ArrayBuffer>;
|
|
33
|
+
a: Int16Array<ArrayBuffer>;
|
|
34
|
+
s: Uint8Array<ArrayBuffer>;
|
|
55
35
|
d: Uint32Array<ArrayBuffer>;
|
|
56
|
-
t: any
|
|
36
|
+
t: any;
|
|
57
37
|
};
|
|
58
|
-
_compactTree(node: any): any
|
|
59
|
-
importData(buffer:
|
|
38
|
+
_compactTree(node: any): any;
|
|
39
|
+
importData(buffer: ArrayBuffer | Uint8Array): {
|
|
60
40
|
version: any;
|
|
61
41
|
dataList: any;
|
|
62
42
|
};
|
|
@@ -79,50 +59,21 @@ export class OfflineCompiler {
|
|
|
79
59
|
descriptors: any;
|
|
80
60
|
}[];
|
|
81
61
|
maximaPointsCluster: {
|
|
82
|
-
rootNode:
|
|
83
|
-
leaf: boolean;
|
|
84
|
-
centerPointIndex: any;
|
|
85
|
-
pointIndexes: any;
|
|
86
|
-
children?: undefined;
|
|
87
|
-
} | {
|
|
88
|
-
leaf: boolean;
|
|
89
|
-
centerPointIndex: any;
|
|
90
|
-
children: any;
|
|
91
|
-
pointIndexes?: undefined;
|
|
92
|
-
};
|
|
62
|
+
rootNode: any;
|
|
93
63
|
};
|
|
94
64
|
minimaPointsCluster: {
|
|
95
|
-
rootNode:
|
|
96
|
-
leaf: boolean;
|
|
97
|
-
centerPointIndex: any;
|
|
98
|
-
pointIndexes: any;
|
|
99
|
-
children?: undefined;
|
|
100
|
-
} | {
|
|
101
|
-
leaf: boolean;
|
|
102
|
-
centerPointIndex: any;
|
|
103
|
-
children: any;
|
|
104
|
-
pointIndexes?: undefined;
|
|
105
|
-
};
|
|
65
|
+
rootNode: any;
|
|
106
66
|
};
|
|
107
67
|
};
|
|
108
|
-
_decolumnarize(col: any, width:
|
|
68
|
+
_decolumnarize(col: any, width: number, height: number): {
|
|
109
69
|
x: number;
|
|
110
70
|
y: number;
|
|
111
71
|
angle: any;
|
|
112
72
|
scale: any;
|
|
113
73
|
descriptors: any;
|
|
114
74
|
}[];
|
|
115
|
-
_expandTree(node: any):
|
|
116
|
-
leaf: boolean;
|
|
117
|
-
centerPointIndex: any;
|
|
118
|
-
pointIndexes: any;
|
|
119
|
-
children?: undefined;
|
|
120
|
-
} | {
|
|
121
|
-
leaf: boolean;
|
|
122
|
-
centerPointIndex: any;
|
|
123
|
-
children: any;
|
|
124
|
-
pointIndexes?: undefined;
|
|
125
|
-
};
|
|
75
|
+
_expandTree(node: any): any;
|
|
126
76
|
destroy(): Promise<void>;
|
|
77
|
+
_pack4Bit(data: Uint8Array): Uint8Array<ArrayBufferLike>;
|
|
78
|
+
_unpack4Bit(packed: Uint8Array, width: number, height: number): Uint8Array<ArrayBuffer>;
|
|
127
79
|
}
|
|
128
|
-
import { WorkerPool } from "./utils/worker-pool.js";
|