@ruvector/cnn 0.1.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/index.js ADDED
@@ -0,0 +1,266 @@
1
+ /**
2
+ * @ruvector/cnn - CNN feature extraction for image embeddings
3
+ *
4
+ * SIMD-optimized image embedding extraction using contrastive learning.
5
+ *
6
+ * @example
7
+ * ```javascript
8
+ * const { CnnEmbedder, InfoNCELoss, SimdOps } = require('@ruvector/cnn');
9
+ *
10
+ * // Create embedder
11
+ * const embedder = new CnnEmbedder({ embeddingDim: 512, normalize: true });
12
+ *
13
+ * // Extract embedding from image data
14
+ * const imageData = new Uint8Array(224 * 224 * 3); // RGB image
15
+ * const embedding = embedder.extract(imageData, 224, 224);
16
+ *
17
+ * // Compute similarity
18
+ * const sim = embedder.cosineSimilarity(embedding1, embedding2);
19
+ * ```
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ let wasm = null;
25
+ let initialized = false;
26
+
27
+ /**
28
+ * Initialize the WASM module
29
+ * @returns {Promise<void>}
30
+ */
31
+ async function init() {
32
+ if (initialized) return;
33
+
34
+ if (typeof window !== 'undefined') {
35
+ // Browser environment
36
+ const wasmModule = await import('./ruvector_cnn_wasm.js');
37
+ await wasmModule.default();
38
+ wasm = wasmModule;
39
+ } else {
40
+ // Node.js environment
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const wasmPath = path.join(__dirname, 'ruvector_cnn_wasm_bg.wasm');
44
+
45
+ if (fs.existsSync(wasmPath)) {
46
+ const wasmModule = require('./ruvector_cnn_wasm.js');
47
+ const wasmBuffer = fs.readFileSync(wasmPath);
48
+ await wasmModule.default(wasmBuffer);
49
+ wasm = wasmModule;
50
+ } else {
51
+ throw new Error('WASM file not found. Run `npm run build` first.');
52
+ }
53
+ }
54
+
55
+ initialized = true;
56
+ }
57
+
58
+ /**
59
+ * CNN Embedder for extracting image features
60
+ */
61
+ class CnnEmbedder {
62
+ /**
63
+ * Create a new CNN embedder
64
+ * @param {Object} [config] - Configuration options
65
+ * @param {number} [config.inputSize=224] - Input image size (square)
66
+ * @param {number} [config.embeddingDim=512] - Output embedding dimension
67
+ * @param {boolean} [config.normalize=true] - L2 normalize embeddings
68
+ */
69
+ constructor(config = {}) {
70
+ if (!initialized) {
71
+ throw new Error('Module not initialized. Call init() first.');
72
+ }
73
+
74
+ const wasmConfig = new wasm.EmbedderConfig();
75
+ wasmConfig.input_size = config.inputSize || 224;
76
+ wasmConfig.embedding_dim = config.embeddingDim || 512;
77
+ wasmConfig.normalize = config.normalize !== false;
78
+
79
+ this._inner = new wasm.WasmCnnEmbedder(wasmConfig);
80
+ this._embeddingDim = wasmConfig.embedding_dim;
81
+ }
82
+
83
+ /**
84
+ * Extract embedding from image data
85
+ * @param {Uint8Array} imageData - RGB image data (row-major, no alpha)
86
+ * @param {number} width - Image width
87
+ * @param {number} height - Image height
88
+ * @returns {Float32Array} - Embedding vector
89
+ */
90
+ extract(imageData, width, height) {
91
+ const result = this._inner.extract(imageData, width, height);
92
+ return new Float32Array(result);
93
+ }
94
+
95
+ /**
96
+ * Compute cosine similarity between two embeddings
97
+ * @param {Float32Array} a - First embedding
98
+ * @param {Float32Array} b - Second embedding
99
+ * @returns {number} - Similarity in [-1, 1]
100
+ */
101
+ cosineSimilarity(a, b) {
102
+ return this._inner.cosine_similarity(a, b);
103
+ }
104
+
105
+ /**
106
+ * Get the embedding dimension
107
+ * @returns {number}
108
+ */
109
+ get embeddingDim() {
110
+ return this._embeddingDim;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * InfoNCE loss for contrastive learning (SimCLR style)
116
+ */
117
+ class InfoNCELoss {
118
+ /**
119
+ * Create InfoNCE loss
120
+ * @param {number} [temperature=0.1] - Temperature parameter
121
+ */
122
+ constructor(temperature = 0.1) {
123
+ if (!initialized) {
124
+ throw new Error('Module not initialized. Call init() first.');
125
+ }
126
+ this._inner = new wasm.WasmInfoNCELoss(temperature);
127
+ }
128
+
129
+ /**
130
+ * Compute loss for embedding pairs
131
+ * @param {Float32Array} embeddings - Flattened [2N, D] array
132
+ * @param {number} batchSize - N (number of pairs)
133
+ * @param {number} dim - D (embedding dimension)
134
+ * @returns {number} - Loss value
135
+ */
136
+ forward(embeddings, batchSize, dim) {
137
+ return this._inner.forward(embeddings, batchSize, dim);
138
+ }
139
+
140
+ /**
141
+ * Get temperature parameter
142
+ * @returns {number}
143
+ */
144
+ get temperature() {
145
+ return this._inner.temperature;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Triplet loss for metric learning
151
+ */
152
+ class TripletLoss {
153
+ /**
154
+ * Create triplet loss
155
+ * @param {number} [margin=1.0] - Margin parameter
156
+ */
157
+ constructor(margin = 1.0) {
158
+ if (!initialized) {
159
+ throw new Error('Module not initialized. Call init() first.');
160
+ }
161
+ this._inner = new wasm.WasmTripletLoss(margin);
162
+ }
163
+
164
+ /**
165
+ * Compute triplet loss
166
+ * @param {Float32Array} anchors - Anchor embeddings [N, D]
167
+ * @param {Float32Array} positives - Positive embeddings [N, D]
168
+ * @param {Float32Array} negatives - Negative embeddings [N, D]
169
+ * @param {number} dim - Embedding dimension D
170
+ * @returns {number} - Loss value
171
+ */
172
+ forward(anchors, positives, negatives, dim) {
173
+ return this._inner.forward(anchors, positives, negatives, dim);
174
+ }
175
+
176
+ /**
177
+ * Get margin parameter
178
+ * @returns {number}
179
+ */
180
+ get margin() {
181
+ return this._inner.margin;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * SIMD-optimized operations
187
+ */
188
+ const SimdOps = {
189
+ /**
190
+ * Compute dot product of two vectors
191
+ * @param {Float32Array} a
192
+ * @param {Float32Array} b
193
+ * @returns {number}
194
+ */
195
+ dotProduct(a, b) {
196
+ if (!initialized) throw new Error('Module not initialized');
197
+ return wasm.SimdOps.dot_product(a, b);
198
+ },
199
+
200
+ /**
201
+ * Apply ReLU activation in-place
202
+ * @param {Float32Array} data
203
+ */
204
+ relu(data) {
205
+ if (!initialized) throw new Error('Module not initialized');
206
+ wasm.SimdOps.relu(data);
207
+ },
208
+
209
+ /**
210
+ * Apply ReLU6 activation in-place
211
+ * @param {Float32Array} data
212
+ */
213
+ relu6(data) {
214
+ if (!initialized) throw new Error('Module not initialized');
215
+ wasm.SimdOps.relu6(data);
216
+ },
217
+
218
+ /**
219
+ * L2 normalize a vector in-place
220
+ * @param {Float32Array} data
221
+ */
222
+ l2Normalize(data) {
223
+ if (!initialized) throw new Error('Module not initialized');
224
+ wasm.SimdOps.l2_normalize(data);
225
+ }
226
+ };
227
+
228
+ /**
229
+ * Layer operations for building custom networks
230
+ */
231
+ const LayerOps = {
232
+ /**
233
+ * Apply batch normalization
234
+ * @param {Float32Array} input - Input data (modified in-place)
235
+ * @param {Float32Array} gamma - Scale parameter
236
+ * @param {Float32Array} beta - Shift parameter
237
+ * @param {Float32Array} mean - Running mean
238
+ * @param {Float32Array} variance - Running variance
239
+ * @param {number} [epsilon=1e-5] - Numerical stability
240
+ */
241
+ batchNorm(input, gamma, beta, mean, variance, epsilon = 1e-5) {
242
+ if (!initialized) throw new Error('Module not initialized');
243
+ wasm.LayerOps.batch_norm(input, gamma, beta, mean, variance, epsilon);
244
+ },
245
+
246
+ /**
247
+ * Apply global average pooling
248
+ * @param {Float32Array} input - Input tensor [C, H, W]
249
+ * @param {number} channels - Number of channels
250
+ * @param {number} spatialSize - H * W
251
+ * @returns {Float32Array} - Output [C]
252
+ */
253
+ globalAvgPool(input, channels, spatialSize) {
254
+ if (!initialized) throw new Error('Module not initialized');
255
+ return new Float32Array(wasm.LayerOps.global_avg_pool(input, channels, spatialSize));
256
+ }
257
+ };
258
+
259
+ module.exports = {
260
+ init,
261
+ CnnEmbedder,
262
+ InfoNCELoss,
263
+ TripletLoss,
264
+ SimdOps,
265
+ LayerOps
266
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ruvector/cnn",
3
+ "version": "0.1.0",
4
+ "description": "CNN feature extraction for image embeddings - SIMD-optimized, pure Rust/WASM",
5
+ "main": "index.js",
6
+ "module": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "ruvector_cnn_wasm_bg.wasm",
12
+ "ruvector_cnn_wasm.js",
13
+ "ruvector_cnn_wasm.d.ts",
14
+ "ruvector_cnn_wasm_bg.wasm.d.ts",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "wasm-pack build ../../crates/ruvector-cnn-wasm --target web --out-dir ../../npm/packages/ruvector-cnn/pkg",
19
+ "postbuild": "cp pkg/* . && rm -rf pkg",
20
+ "test": "node test.js"
21
+ },
22
+ "keywords": [
23
+ "cnn",
24
+ "embeddings",
25
+ "image",
26
+ "wasm",
27
+ "simd",
28
+ "machine-learning",
29
+ "contrastive-learning",
30
+ "mobilenet",
31
+ "ruvector"
32
+ ],
33
+ "author": "ruvnet",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/ruvnet/ruvector.git",
38
+ "directory": "npm/packages/ruvector-cnn"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/ruvnet/ruvector/issues"
42
+ },
43
+ "homepage": "https://github.com/ruvnet/ruvector#readme",
44
+ "engines": {
45
+ "node": ">=16.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
@@ -0,0 +1,204 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Configuration for CNN embedder
6
+ */
7
+ export class EmbedderConfig {
8
+ free(): void;
9
+ [Symbol.dispose](): void;
10
+ constructor();
11
+ /**
12
+ * Output embedding dimension
13
+ */
14
+ embedding_dim: number;
15
+ /**
16
+ * Input image size (square)
17
+ */
18
+ input_size: number;
19
+ /**
20
+ * Whether to L2 normalize embeddings
21
+ */
22
+ normalize: boolean;
23
+ }
24
+
25
+ /**
26
+ * Layer operations for building custom networks
27
+ */
28
+ export class LayerOps {
29
+ private constructor();
30
+ free(): void;
31
+ [Symbol.dispose](): void;
32
+ /**
33
+ * Apply batch normalization (returns new array)
34
+ */
35
+ static batch_norm(input: Float32Array, gamma: Float32Array, beta: Float32Array, mean: Float32Array, _var: Float32Array, epsilon: number): Float32Array;
36
+ /**
37
+ * Apply global average pooling
38
+ * Returns one value per channel
39
+ */
40
+ static global_avg_pool(input: Float32Array, height: number, width: number, channels: number): Float32Array;
41
+ }
42
+
43
+ /**
44
+ * SIMD-optimized operations
45
+ */
46
+ export class SimdOps {
47
+ private constructor();
48
+ free(): void;
49
+ [Symbol.dispose](): void;
50
+ /**
51
+ * Dot product of two vectors
52
+ */
53
+ static dot_product(a: Float32Array, b: Float32Array): number;
54
+ /**
55
+ * L2 normalize a vector (returns new array)
56
+ */
57
+ static l2_normalize(data: Float32Array): Float32Array;
58
+ /**
59
+ * ReLU activation (returns new array)
60
+ */
61
+ static relu(data: Float32Array): Float32Array;
62
+ /**
63
+ * ReLU6 activation (returns new array)
64
+ */
65
+ static relu6(data: Float32Array): Float32Array;
66
+ }
67
+
68
+ /**
69
+ * WASM CNN Embedder for image feature extraction
70
+ */
71
+ export class WasmCnnEmbedder {
72
+ free(): void;
73
+ [Symbol.dispose](): void;
74
+ /**
75
+ * Compute cosine similarity between two embeddings
76
+ */
77
+ cosine_similarity(a: Float32Array, b: Float32Array): number;
78
+ /**
79
+ * Extract embedding from image data (RGB format, row-major)
80
+ */
81
+ extract(image_data: Uint8Array, width: number, height: number): Float32Array;
82
+ /**
83
+ * Create a new CNN embedder
84
+ */
85
+ constructor(config?: EmbedderConfig | null);
86
+ /**
87
+ * Get the embedding dimension
88
+ */
89
+ readonly embedding_dim: number;
90
+ }
91
+
92
+ /**
93
+ * InfoNCE loss for contrastive learning (SimCLR style)
94
+ */
95
+ export class WasmInfoNCELoss {
96
+ free(): void;
97
+ [Symbol.dispose](): void;
98
+ /**
99
+ * Compute loss for a batch of embedding pairs
100
+ * embeddings: [2N, D] flattened where (i, i+N) are positive pairs
101
+ */
102
+ forward(embeddings: Float32Array, batch_size: number, dim: number): number;
103
+ /**
104
+ * Create new InfoNCE loss with temperature parameter
105
+ */
106
+ constructor(temperature: number);
107
+ /**
108
+ * Get the temperature parameter
109
+ */
110
+ readonly temperature: number;
111
+ }
112
+
113
+ /**
114
+ * Triplet loss for metric learning
115
+ */
116
+ export class WasmTripletLoss {
117
+ free(): void;
118
+ [Symbol.dispose](): void;
119
+ /**
120
+ * Compute loss for a batch of triplets
121
+ */
122
+ forward(anchors: Float32Array, positives: Float32Array, negatives: Float32Array, dim: number): number;
123
+ /**
124
+ * Compute loss for a single triplet
125
+ */
126
+ forward_single(anchor: Float32Array, positive: Float32Array, negative: Float32Array): number;
127
+ /**
128
+ * Create new triplet loss with margin
129
+ */
130
+ constructor(margin: number);
131
+ /**
132
+ * Get the margin parameter
133
+ */
134
+ readonly margin: number;
135
+ }
136
+
137
+ /**
138
+ * Initialize panic hook for better error messages
139
+ */
140
+ export function init(): void;
141
+
142
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
143
+
144
+ export interface InitOutput {
145
+ readonly memory: WebAssembly.Memory;
146
+ readonly __wbg_embedderconfig_free: (a: number, b: number) => void;
147
+ readonly __wbg_get_embedderconfig_embedding_dim: (a: number) => number;
148
+ readonly __wbg_get_embedderconfig_input_size: (a: number) => number;
149
+ readonly __wbg_get_embedderconfig_normalize: (a: number) => number;
150
+ readonly __wbg_layerops_free: (a: number, b: number) => void;
151
+ readonly __wbg_set_embedderconfig_embedding_dim: (a: number, b: number) => void;
152
+ readonly __wbg_set_embedderconfig_input_size: (a: number, b: number) => void;
153
+ readonly __wbg_set_embedderconfig_normalize: (a: number, b: number) => void;
154
+ readonly __wbg_wasmcnnembedder_free: (a: number, b: number) => void;
155
+ readonly __wbg_wasminfonceloss_free: (a: number, b: number) => void;
156
+ readonly __wbg_wasmtripletloss_free: (a: number, b: number) => void;
157
+ readonly embedderconfig_new: () => number;
158
+ readonly layerops_batch_norm: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number) => void;
159
+ readonly layerops_global_avg_pool: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
160
+ readonly simdops_dot_product: (a: number, b: number, c: number, d: number) => number;
161
+ readonly simdops_l2_normalize: (a: number, b: number, c: number) => void;
162
+ readonly simdops_relu: (a: number, b: number, c: number) => void;
163
+ readonly simdops_relu6: (a: number, b: number, c: number) => void;
164
+ readonly wasmcnnembedder_cosine_similarity: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
165
+ readonly wasmcnnembedder_embedding_dim: (a: number) => number;
166
+ readonly wasmcnnembedder_extract: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
167
+ readonly wasmcnnembedder_new: (a: number, b: number) => void;
168
+ readonly wasminfonceloss_forward: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
169
+ readonly wasminfonceloss_new: (a: number) => number;
170
+ readonly wasminfonceloss_temperature: (a: number) => number;
171
+ readonly wasmtripletloss_forward: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
172
+ readonly wasmtripletloss_forward_single: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
173
+ readonly wasmtripletloss_margin: (a: number) => number;
174
+ readonly wasmtripletloss_new: (a: number) => number;
175
+ readonly init: () => void;
176
+ readonly __wbg_simdops_free: (a: number, b: number) => void;
177
+ readonly __wbindgen_export: (a: number, b: number, c: number) => void;
178
+ readonly __wbindgen_export2: (a: number, b: number) => number;
179
+ readonly __wbindgen_export3: (a: number, b: number, c: number, d: number) => number;
180
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
181
+ readonly __wbindgen_start: () => void;
182
+ }
183
+
184
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
185
+
186
+ /**
187
+ * Instantiates the given `module`, which can either be bytes or
188
+ * a precompiled `WebAssembly.Module`.
189
+ *
190
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
191
+ *
192
+ * @returns {InitOutput}
193
+ */
194
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
195
+
196
+ /**
197
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
198
+ * for everything else, calls `WebAssembly.instantiate` directly.
199
+ *
200
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
201
+ *
202
+ * @returns {Promise<InitOutput>}
203
+ */
204
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;