rtmlib-ts 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.gitattributes +1 -0
  2. package/README.md +202 -0
  3. package/dist/core/base.d.ts +20 -0
  4. package/dist/core/base.d.ts.map +1 -0
  5. package/dist/core/base.js +40 -0
  6. package/dist/core/file.d.ts +11 -0
  7. package/dist/core/file.d.ts.map +1 -0
  8. package/dist/core/file.js +111 -0
  9. package/dist/core/modelCache.d.ts +35 -0
  10. package/dist/core/modelCache.d.ts.map +1 -0
  11. package/dist/core/modelCache.js +161 -0
  12. package/dist/core/posePostprocessing.d.ts +12 -0
  13. package/dist/core/posePostprocessing.d.ts.map +1 -0
  14. package/dist/core/posePostprocessing.js +76 -0
  15. package/dist/core/postprocessing.d.ts +10 -0
  16. package/dist/core/postprocessing.d.ts.map +1 -0
  17. package/dist/core/postprocessing.js +70 -0
  18. package/dist/core/preprocessing.d.ts +14 -0
  19. package/dist/core/preprocessing.d.ts.map +1 -0
  20. package/dist/core/preprocessing.js +79 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +31 -0
  24. package/dist/models/rtmpose.d.ts +25 -0
  25. package/dist/models/rtmpose.d.ts.map +1 -0
  26. package/dist/models/rtmpose.js +185 -0
  27. package/dist/models/rtmpose3d.d.ts +28 -0
  28. package/dist/models/rtmpose3d.d.ts.map +1 -0
  29. package/dist/models/rtmpose3d.js +184 -0
  30. package/dist/models/yolo12.d.ts +23 -0
  31. package/dist/models/yolo12.d.ts.map +1 -0
  32. package/dist/models/yolo12.js +165 -0
  33. package/dist/models/yolox.d.ts +18 -0
  34. package/dist/models/yolox.d.ts.map +1 -0
  35. package/dist/models/yolox.js +167 -0
  36. package/dist/solution/animalDetector.d.ts +229 -0
  37. package/dist/solution/animalDetector.d.ts.map +1 -0
  38. package/dist/solution/animalDetector.js +663 -0
  39. package/dist/solution/body.d.ts +16 -0
  40. package/dist/solution/body.d.ts.map +1 -0
  41. package/dist/solution/body.js +52 -0
  42. package/dist/solution/bodyWithFeet.d.ts +16 -0
  43. package/dist/solution/bodyWithFeet.d.ts.map +1 -0
  44. package/dist/solution/bodyWithFeet.js +52 -0
  45. package/dist/solution/customDetector.d.ts +137 -0
  46. package/dist/solution/customDetector.d.ts.map +1 -0
  47. package/dist/solution/customDetector.js +342 -0
  48. package/dist/solution/hand.d.ts +14 -0
  49. package/dist/solution/hand.d.ts.map +1 -0
  50. package/dist/solution/hand.js +20 -0
  51. package/dist/solution/index.d.ts +10 -0
  52. package/dist/solution/index.d.ts.map +1 -0
  53. package/dist/solution/index.js +9 -0
  54. package/dist/solution/objectDetector.d.ts +172 -0
  55. package/dist/solution/objectDetector.d.ts.map +1 -0
  56. package/dist/solution/objectDetector.js +606 -0
  57. package/dist/solution/pose3dDetector.d.ts +145 -0
  58. package/dist/solution/pose3dDetector.d.ts.map +1 -0
  59. package/dist/solution/pose3dDetector.js +611 -0
  60. package/dist/solution/poseDetector.d.ts +198 -0
  61. package/dist/solution/poseDetector.d.ts.map +1 -0
  62. package/dist/solution/poseDetector.js +622 -0
  63. package/dist/solution/poseTracker.d.ts +22 -0
  64. package/dist/solution/poseTracker.d.ts.map +1 -0
  65. package/dist/solution/poseTracker.js +106 -0
  66. package/dist/solution/wholebody.d.ts +19 -0
  67. package/dist/solution/wholebody.d.ts.map +1 -0
  68. package/dist/solution/wholebody.js +82 -0
  69. package/dist/solution/wholebody3d.d.ts +22 -0
  70. package/dist/solution/wholebody3d.d.ts.map +1 -0
  71. package/dist/solution/wholebody3d.js +75 -0
  72. package/dist/types/index.d.ts +52 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/types/index.js +5 -0
  75. package/dist/visualization/draw.d.ts +57 -0
  76. package/dist/visualization/draw.d.ts.map +1 -0
  77. package/dist/visualization/draw.js +400 -0
  78. package/dist/visualization/skeleton/coco133.d.ts +350 -0
  79. package/dist/visualization/skeleton/coco133.d.ts.map +1 -0
  80. package/dist/visualization/skeleton/coco133.js +120 -0
  81. package/dist/visualization/skeleton/coco17.d.ts +180 -0
  82. package/dist/visualization/skeleton/coco17.d.ts.map +1 -0
  83. package/dist/visualization/skeleton/coco17.js +48 -0
  84. package/dist/visualization/skeleton/halpe26.d.ts +278 -0
  85. package/dist/visualization/skeleton/halpe26.d.ts.map +1 -0
  86. package/dist/visualization/skeleton/halpe26.js +70 -0
  87. package/dist/visualization/skeleton/hand21.d.ts +196 -0
  88. package/dist/visualization/skeleton/hand21.d.ts.map +1 -0
  89. package/dist/visualization/skeleton/hand21.js +51 -0
  90. package/dist/visualization/skeleton/index.d.ts +10 -0
  91. package/dist/visualization/skeleton/index.d.ts.map +1 -0
  92. package/dist/visualization/skeleton/index.js +9 -0
  93. package/dist/visualization/skeleton/openpose134.d.ts +357 -0
  94. package/dist/visualization/skeleton/openpose134.d.ts.map +1 -0
  95. package/dist/visualization/skeleton/openpose134.js +116 -0
  96. package/dist/visualization/skeleton/openpose18.d.ts +177 -0
  97. package/dist/visualization/skeleton/openpose18.d.ts.map +1 -0
  98. package/dist/visualization/skeleton/openpose18.js +47 -0
  99. package/docs/ANIMAL_DETECTOR.md +450 -0
  100. package/docs/CUSTOM_DETECTOR.md +568 -0
  101. package/docs/OBJECT_DETECTOR.md +373 -0
  102. package/docs/POSE3D_DETECTOR.md +458 -0
  103. package/docs/POSE_DETECTOR.md +442 -0
  104. package/examples/README.md +119 -0
  105. package/examples/index.html +746 -0
  106. package/package.json +51 -0
  107. package/playground/README.md +114 -0
  108. package/playground/app/favicon.ico +0 -0
  109. package/playground/app/globals.css +17 -0
  110. package/playground/app/layout.tsx +19 -0
  111. package/playground/app/page.tsx +1338 -0
  112. package/playground/eslint.config.mjs +18 -0
  113. package/playground/next.config.ts +34 -0
  114. package/playground/package-lock.json +6723 -0
  115. package/playground/package.json +27 -0
  116. package/playground/postcss.config.mjs +7 -0
  117. package/playground/tsconfig.json +34 -0
  118. package/src/core/base.ts +66 -0
  119. package/src/core/file.ts +141 -0
  120. package/src/core/modelCache.ts +189 -0
  121. package/src/core/posePostprocessing.ts +91 -0
  122. package/src/core/postprocessing.ts +93 -0
  123. package/src/core/preprocessing.ts +127 -0
  124. package/src/index.ts +69 -0
  125. package/src/models/rtmpose.ts +265 -0
  126. package/src/models/rtmpose3d.ts +289 -0
  127. package/src/models/yolo12.ts +220 -0
  128. package/src/models/yolox.ts +214 -0
  129. package/src/solution/animalDetector.ts +955 -0
  130. package/src/solution/body.ts +89 -0
  131. package/src/solution/bodyWithFeet.ts +89 -0
  132. package/src/solution/customDetector.ts +474 -0
  133. package/src/solution/hand.ts +52 -0
  134. package/src/solution/index.ts +10 -0
  135. package/src/solution/objectDetector.ts +816 -0
  136. package/src/solution/pose3dDetector.ts +890 -0
  137. package/src/solution/poseDetector.ts +892 -0
  138. package/src/solution/poseTracker.ts +172 -0
  139. package/src/solution/wholebody.ts +130 -0
  140. package/src/solution/wholebody3d.ts +125 -0
  141. package/src/types/index.ts +62 -0
  142. package/src/visualization/draw.ts +543 -0
  143. package/src/visualization/skeleton/coco133.ts +131 -0
  144. package/src/visualization/skeleton/coco17.ts +49 -0
  145. package/src/visualization/skeleton/halpe26.ts +71 -0
  146. package/src/visualization/skeleton/hand21.ts +52 -0
  147. package/src/visualization/skeleton/index.ts +10 -0
  148. package/src/visualization/skeleton/openpose134.ts +125 -0
  149. package/src/visualization/skeleton/openpose18.ts +48 -0
  150. package/tsconfig.json +32 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "playground",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "next": "16.1.6",
13
+ "react": "19.2.3",
14
+ "react-dom": "19.2.3",
15
+ "onnxruntime-web": "^1.23.0"
16
+ },
17
+ "devDependencies": {
18
+ "@tailwindcss/postcss": "^4",
19
+ "@types/node": "^20",
20
+ "@types/react": "^19",
21
+ "@types/react-dom": "^19",
22
+ "eslint": "^9",
23
+ "eslint-config-next": "16.1.6",
24
+ "tailwindcss": "^4",
25
+ "typescript": "^5"
26
+ }
27
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * BaseTool - Abstract base class for all models
3
+ * Handles ONNX model loading and inference
4
+ * Compatible with onnxruntime-web (browser) and onnxruntime-node
5
+ */
6
+
7
+ import * as ort from 'onnxruntime-web';
8
+ import { BackendType } from '../types/index.js';
9
+
10
+ export abstract class BaseTool {
11
+ protected session: ort.InferenceSession | null = null;
12
+ protected modelPath: string;
13
+ protected modelInputSize: [number, number];
14
+ protected mean: number[] | null;
15
+ protected std: number[] | null;
16
+ protected backend: BackendType;
17
+
18
+ constructor(
19
+ modelPath: string,
20
+ modelInputSize: [number, number],
21
+ mean: number[] | null = null,
22
+ std: number[] | null = null,
23
+ backend: BackendType = 'webgpu'
24
+ ) {
25
+ this.modelPath = modelPath;
26
+ this.modelInputSize = modelInputSize;
27
+ this.mean = mean;
28
+ this.std = std;
29
+ this.backend = backend;
30
+ }
31
+
32
+ protected async init(): Promise<void> {
33
+ // Configure ONNX Runtime Web - use CDN for WASM files
34
+ ort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/';
35
+ ort.env.wasm.simd = true;
36
+ ort.env.wasm.proxy = false;
37
+
38
+ // Load model from path/URL
39
+ this.session = await ort.InferenceSession.create(this.modelPath, {
40
+ executionProviders: ['wasm'],
41
+ graphOptimizationLevel: 'all',
42
+ });
43
+
44
+ console.log(`Loaded model: ${this.modelPath}`);
45
+ }
46
+
47
+ protected async inference(img: Float32Array, inputSize?: [number, number]): Promise<any[]> {
48
+ if (!this.session) {
49
+ throw new Error('Session not initialized. Call init() first.');
50
+ }
51
+
52
+ const [h, w] = inputSize || this.modelInputSize;
53
+
54
+ // Build input tensor (1, 3, H, W)
55
+ const inputTensor = new (await import('onnxruntime-web')).Tensor('float32', img, [1, 3, h, w]);
56
+
57
+ const feeds: Record<string, any> = {};
58
+ feeds[this.session.inputNames[0]] = inputTensor;
59
+
60
+ const results = await this.session.run(feeds);
61
+
62
+ return this.session.outputNames.map((name: string) => results[name]);
63
+ }
64
+
65
+ abstract call(...args: unknown[]): Promise<unknown>;
66
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * File utilities for downloading and loading models
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as https from 'https';
8
+ import JSZip from 'jszip';
9
+
10
+ const CACHE_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.rtmlib', 'models');
11
+
12
+ export async function downloadCheckpoint(url: string, localPath?: string): Promise<string> {
13
+ // If local path provided, use it directly
14
+ if (localPath && fs.existsSync(localPath)) {
15
+ console.log(`Using local model: ${localPath}`);
16
+ return localPath;
17
+ }
18
+
19
+ const fileName = path.basename(url);
20
+ const cachePath = path.join(CACHE_DIR, fileName.replace('.zip', '.onnx'));
21
+
22
+ if (fs.existsSync(cachePath)) {
23
+ console.log(`Using cached model: ${cachePath}`);
24
+ return cachePath;
25
+ }
26
+
27
+ console.log(`Downloading model from ${url}`);
28
+
29
+ if (!fs.existsSync(CACHE_DIR)) {
30
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
31
+ }
32
+
33
+ const tempPath = path.join(CACHE_DIR, fileName);
34
+
35
+ await downloadFile(url, tempPath);
36
+
37
+ if (fileName.endsWith('.zip')) {
38
+ await extractZip(tempPath, CACHE_DIR);
39
+ fs.unlinkSync(tempPath);
40
+ }
41
+
42
+ return cachePath;
43
+ }
44
+
45
+ async function downloadFile(url: string, dest: string): Promise<void> {
46
+ return new Promise((resolve, reject) => {
47
+ const file = fs.createWriteStream(dest);
48
+
49
+ const download = (url: string) => {
50
+ https.get(url, (response) => {
51
+ if (response.statusCode === 302 || response.statusCode === 301) {
52
+ download(response.headers.location!);
53
+ return;
54
+ }
55
+
56
+ response.pipe(file);
57
+ file.on('finish', () => {
58
+ file.close();
59
+ resolve();
60
+ });
61
+ }).on('error', reject);
62
+ };
63
+
64
+ download(url);
65
+ });
66
+ }
67
+
68
+ async function extractZip(zipPath: string, dest: string): Promise<void> {
69
+ const data = fs.readFileSync(zipPath);
70
+ const zip = await JSZip.loadAsync(data);
71
+
72
+ // Find .onnx file in zip
73
+ for (const [filename, file] of Object.entries(zip.files)) {
74
+ if (filename.endsWith('.onnx')) {
75
+ const content = await file.async('nodebuffer');
76
+ const onnxPath = path.join(dest, filename);
77
+
78
+ // Create directory if needed
79
+ const dir = path.dirname(onnxPath);
80
+ if (!fs.existsSync(dir)) {
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ }
83
+
84
+ fs.writeFileSync(onnxPath, content);
85
+ console.log(`Extracted: ${filename}`);
86
+ return;
87
+ }
88
+ }
89
+
90
+ throw new Error('No .onnx file found in zip');
91
+ }
92
+
93
+ export function fileExists(filePath: string): boolean {
94
+ return fs.existsSync(filePath);
95
+ }
96
+
97
+ export function resolveModelPath(modelPath: string): string {
98
+ if (modelPath.startsWith('http://') || modelPath.startsWith('https://')) {
99
+ return modelPath;
100
+ }
101
+ return path.resolve(modelPath);
102
+ }
103
+
104
+ /**
105
+ * Extract local zip file and return onnx path
106
+ */
107
+ export async function extractLocalZip(zipPath: string): Promise<string> {
108
+ if (!fs.existsSync(zipPath)) {
109
+ throw new Error(`Zip file not found: ${zipPath}`);
110
+ }
111
+
112
+ const destDir = path.dirname(zipPath);
113
+ const data = fs.readFileSync(zipPath);
114
+ const zip = await JSZip.loadAsync(data);
115
+
116
+ // Find .onnx file in zip
117
+ for (const [filename, file] of Object.entries(zip.files)) {
118
+ if (filename.endsWith('.onnx')) {
119
+ const onnxPath = path.join(destDir, filename);
120
+
121
+ if (fs.existsSync(onnxPath)) {
122
+ console.log(`Using extracted model: ${onnxPath}`);
123
+ return onnxPath;
124
+ }
125
+
126
+ const content = await file.async('nodebuffer');
127
+
128
+ // Create directory if needed
129
+ const dir = path.dirname(onnxPath);
130
+ if (!fs.existsSync(dir)) {
131
+ fs.mkdirSync(dir, { recursive: true });
132
+ }
133
+
134
+ fs.writeFileSync(onnxPath, content);
135
+ console.log(`Extracted: ${filename} -> ${onnxPath}`);
136
+ return onnxPath;
137
+ }
138
+ }
139
+
140
+ throw new Error('No .onnx file found in zip');
141
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Model caching utility using Cache API
3
+ * Caches ONNX models in browser to avoid repeated downloads
4
+ */
5
+
6
+ const CACHE_NAME = 'rtmlib-ts-models-v1';
7
+
8
+ /**
9
+ * Check if model is available in cache
10
+ */
11
+ export async function isModelCached(url: string): Promise<boolean> {
12
+ if (typeof caches === 'undefined') {
13
+ // Cache API not available (e.g., Node.js)
14
+ return false;
15
+ }
16
+
17
+ try {
18
+ const cache = await caches.open(CACHE_NAME);
19
+ const response = await cache.match(url);
20
+ return !!response;
21
+ } catch (error) {
22
+ console.warn(`[ModelCache] Failed to check cache for ${url}:`, error);
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Get model from cache or fetch from network
29
+ * @param url - Model URL
30
+ * @param forceRefresh - Force refresh from network
31
+ */
32
+ export async function getCachedModel(url: string, forceRefresh: boolean = false): Promise<ArrayBuffer> {
33
+ if (typeof caches === 'undefined') {
34
+ // Cache API not available, fetch directly
35
+ console.log(`[ModelCache] Cache API not available, fetching from network`);
36
+ return fetchModelFromNetwork(url);
37
+ }
38
+
39
+ try {
40
+ const cache = await caches.open(CACHE_NAME);
41
+
42
+ // Try to get from cache first
43
+ if (!forceRefresh) {
44
+ const cachedResponse = await cache.match(url);
45
+ if (cachedResponse) {
46
+ console.log(`[ModelCache] ✅ Hit for ${url}`);
47
+ return await cachedResponse.arrayBuffer();
48
+ }
49
+ console.log(`[ModelCache] ❌ Miss for ${url}, fetching from network...`);
50
+ }
51
+
52
+ // Fetch from network
53
+ const networkResponse = await fetchModelFromNetwork(url);
54
+
55
+ // Cache the response
56
+ const responseToCache = new Response(networkResponse, {
57
+ headers: {
58
+ 'Content-Type': 'application/octet-stream',
59
+ },
60
+ });
61
+
62
+ await cache.put(url, responseToCache);
63
+ console.log(`[ModelCache] 💾 Cached ${url}`);
64
+
65
+ return networkResponse;
66
+ } catch (error) {
67
+ console.error(`[ModelCache] Failed to get/cache model ${url}:`, error);
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Fetch model from network with progress tracking
74
+ */
75
+ async function fetchModelFromNetwork(url: string): Promise<ArrayBuffer> {
76
+ const response = await fetch(url);
77
+
78
+ if (!response.ok) {
79
+ throw new Error(`Failed to fetch model: HTTP ${response.status} ${response.statusText}`);
80
+ }
81
+
82
+ return await response.arrayBuffer();
83
+ }
84
+
85
+ /**
86
+ * Preload and cache multiple models
87
+ */
88
+ export async function preloadModels(urls: string[]): Promise<void> {
89
+ console.log(`[ModelCache] Preloading ${urls.length} model(s)...`);
90
+
91
+ const results = await Promise.allSettled(
92
+ urls.map(url => getCachedModel(url))
93
+ );
94
+
95
+ const success = results.filter(r => r.status === 'fulfilled').length;
96
+ const failed = results.filter(r => r.status === 'rejected').length;
97
+
98
+ console.log(`[ModelCache] Preload complete: ${success} succeeded, ${failed} failed`);
99
+
100
+ results.forEach((result, index) => {
101
+ if (result.status === 'rejected') {
102
+ console.error(`[ModelCache] Failed to preload ${urls[index]}:`, result.reason);
103
+ }
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Clear all cached models
109
+ */
110
+ export async function clearModelCache(): Promise<void> {
111
+ if (typeof caches === 'undefined') {
112
+ return;
113
+ }
114
+
115
+ try {
116
+ await caches.delete(CACHE_NAME);
117
+ console.log('[ModelCache] Cache cleared');
118
+ } catch (error) {
119
+ console.error('[ModelCache] Failed to clear cache:', error);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Get cache size in bytes
125
+ */
126
+ export async function getCacheSize(): Promise<number> {
127
+ if (typeof caches === 'undefined' || !navigator.storage) {
128
+ return 0;
129
+ }
130
+
131
+ try {
132
+ const cache = await caches.open(CACHE_NAME);
133
+ const keys = await cache.keys();
134
+ let totalSize = 0;
135
+
136
+ for (const request of keys) {
137
+ const response = await cache.match(request);
138
+ if (response) {
139
+ const blob = await response.blob();
140
+ totalSize += blob.size;
141
+ }
142
+ }
143
+
144
+ return totalSize;
145
+ } catch (error) {
146
+ console.warn('[ModelCache] Failed to get cache size:', error);
147
+ return 0;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Get cache info
153
+ */
154
+ export async function getCacheInfo(): Promise<{
155
+ cachedModels: string[];
156
+ totalSize: number;
157
+ totalSizeFormatted: string;
158
+ }> {
159
+ if (typeof caches === 'undefined') {
160
+ return { cachedModels: [], totalSize: 0, totalSizeFormatted: '0 B' };
161
+ }
162
+
163
+ try {
164
+ const cache = await caches.open(CACHE_NAME);
165
+ const keys = await cache.keys();
166
+ const cachedModels = keys.map(k => k.url);
167
+ const totalSize = await getCacheSize();
168
+
169
+ return {
170
+ cachedModels,
171
+ totalSize,
172
+ totalSizeFormatted: formatBytes(totalSize),
173
+ };
174
+ } catch (error) {
175
+ console.warn('[ModelCache] Failed to get cache info:', error);
176
+ return { cachedModels: [], totalSize: 0, totalSizeFormatted: '0 B' };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Format bytes to human-readable string
182
+ */
183
+ function formatBytes(bytes: number): string {
184
+ if (bytes === 0) return '0 B';
185
+ const k = 1024;
186
+ const sizes = ['B', 'KB', 'MB', 'GB'];
187
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
188
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
189
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Post-processing utilities for pose estimation
3
+ */
4
+
5
+ export function getSimccMaximum(
6
+ simccX: Float32Array,
7
+ simccY: Float32Array
8
+ ): { locations: number[]; scores: number[] } {
9
+ const numKeypoints = simccX.length / 2; // Assuming split_ratio = 2
10
+
11
+ const locations: number[] = [];
12
+ const scores: number[] = [];
13
+
14
+ for (let i = 0; i < numKeypoints; i++) {
15
+ // Find argmax for x
16
+ let maxX = -Infinity;
17
+ let argmaxX = 0;
18
+ for (let j = 0; j < 2; j++) {
19
+ const val = simccX[i * 2 + j];
20
+ if (val > maxX) {
21
+ maxX = val;
22
+ argmaxX = j;
23
+ }
24
+ }
25
+
26
+ // Find argmax for y
27
+ let maxY = -Infinity;
28
+ let argmaxY = 0;
29
+ for (let j = 0; j < 2; j++) {
30
+ const val = simccY[i * 2 + j];
31
+ if (val > maxY) {
32
+ maxY = val;
33
+ argmaxY = j;
34
+ }
35
+ }
36
+
37
+ locations.push(argmaxX, argmaxY);
38
+ scores.push((maxX + maxY) / 2);
39
+ }
40
+
41
+ return { locations, scores };
42
+ }
43
+
44
+ export function convertCocoToOpenpose(
45
+ keypoints: number[][],
46
+ scores: number[]
47
+ ): { keypoints: number[][]; scores: number[] } {
48
+ // COCO 17 keypoints to OpenPose 18 keypoints mapping
49
+ const cocoToOpenpose: number[] = [
50
+ 0, // nose
51
+ 1, // neck (average of shoulders)
52
+ 2, // right_shoulder
53
+ 3, // right_elbow
54
+ 4, // right_wrist
55
+ 5, // left_shoulder
56
+ 6, // left_elbow
57
+ 7, // left_wrist
58
+ 8, // right_hip
59
+ 9, // right_knee
60
+ 10, // right_ankle
61
+ 11, // left_hip
62
+ 12, // left_knee
63
+ 13, // left_ankle
64
+ 14, // right_eye
65
+ 15, // left_eye
66
+ 16, // right_ear
67
+ 17, // left_ear
68
+ ];
69
+
70
+ const openposeKeypoints: number[][] = [];
71
+ const openposeScores: number[] = [];
72
+
73
+ for (let i = 0; i < 18; i++) {
74
+ if (i === 1) {
75
+ // Neck is average of shoulders
76
+ const rightShoulder = keypoints[2];
77
+ const leftShoulder = keypoints[5];
78
+ openposeKeypoints.push([
79
+ (rightShoulder[0] + leftShoulder[0]) / 2,
80
+ (rightShoulder[1] + leftShoulder[1]) / 2,
81
+ ]);
82
+ openposeScores.push((scores[2] + scores[5]) / 2);
83
+ } else {
84
+ const cocoIdx = cocoToOpenpose[i];
85
+ openposeKeypoints.push([...keypoints[cocoIdx]]);
86
+ openposeScores.push(scores[cocoIdx]);
87
+ }
88
+ }
89
+
90
+ return { keypoints: openposeKeypoints, scores: openposeScores };
91
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Post-processing utilities for object detection
3
+ */
4
+
5
+ import { BBox } from '../types/index.js';
6
+
7
+ export function multiclassNms(
8
+ boxes: number[][],
9
+ scores: number[][],
10
+ nmsThr: number,
11
+ scoreThr: number
12
+ ): { boxes: number[][]; scores: number[]; classIds: number[] } | null {
13
+ const numClasses = scores[0].length;
14
+ const numBoxes = boxes.length;
15
+
16
+ const allDetections: Array<{ box: number[]; score: number; classId: number }> = [];
17
+
18
+ for (let c = 0; c < numClasses; c++) {
19
+ for (let i = 0; i < numBoxes; i++) {
20
+ if (scores[i][c] > scoreThr) {
21
+ allDetections.push({
22
+ box: boxes[i],
23
+ score: scores[i][c],
24
+ classId: c,
25
+ });
26
+ }
27
+ }
28
+ }
29
+
30
+ allDetections.sort((a, b) => b.score - a.score);
31
+
32
+ const keep: typeof allDetections = [];
33
+
34
+ while (allDetections.length > 0) {
35
+ const current = allDetections.shift()!;
36
+ keep.push(current);
37
+
38
+ for (let i = allDetections.length - 1; i >= 0; i--) {
39
+ const other = allDetections[i];
40
+ if (current.classId !== other.classId) continue;
41
+
42
+ const iou = calculateIoU(current.box, other.box);
43
+ if (iou > nmsThr) {
44
+ allDetections.splice(i, 1);
45
+ }
46
+ }
47
+ }
48
+
49
+ if (keep.length === 0) return null;
50
+
51
+ return {
52
+ boxes: keep.map(d => d.box),
53
+ scores: keep.map(d => d.score),
54
+ classIds: keep.map(d => d.classId),
55
+ };
56
+ }
57
+
58
+ function calculateIoU(box1: number[], box2: number[]): number {
59
+ const x1 = Math.max(box1[0], box2[0]);
60
+ const y1 = Math.max(box1[1], box2[1]);
61
+ const x2 = Math.min(box1[2], box2[2]);
62
+ const y2 = Math.min(box1[3], box2[3]);
63
+
64
+ const interWidth = Math.max(0, x2 - x1);
65
+ const interHeight = Math.max(0, y2 - y1);
66
+ const interArea = interWidth * interHeight;
67
+
68
+ const box1Area = (box1[2] - box1[0]) * (box1[3] - box1[1]);
69
+ const box2Area = (box2[2] - box2[0]) * (box2[3] - box2[1]);
70
+
71
+ const unionArea = box1Area + box2Area - interArea;
72
+ return unionArea > 0 ? interArea / unionArea : 0;
73
+ }
74
+
75
+ export function nms(boxes: number[][], scores: number[], nmsThr: number): number[] {
76
+ const indices: number[] = [];
77
+ const sortedIndices = scores.map((s, i) => i).sort((a, b) => scores[b] - scores[a]);
78
+
79
+ while (sortedIndices.length > 0) {
80
+ const current = sortedIndices.shift()!;
81
+ indices.push(current);
82
+
83
+ for (let i = sortedIndices.length - 1; i >= 0; i--) {
84
+ const other = sortedIndices[i];
85
+ const iou = calculateIoU(boxes[current], boxes[other]);
86
+ if (iou > nmsThr) {
87
+ sortedIndices.splice(i, 1);
88
+ }
89
+ }
90
+ }
91
+
92
+ return indices;
93
+ }