@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/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # @ruvector/cnn
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ruvector/cnn.svg)](https://www.npmjs.com/package/@ruvector/cnn)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Turn images into searchable vectors** — runs in browsers, no backend needed.
7
+
8
+ ## What Does This Do?
9
+
10
+ This package converts images into numbers (called "embeddings") that describe what's in the picture. Similar images produce similar numbers.
11
+
12
+ **Use it to:**
13
+ - Build "find similar images" features
14
+ - Group photos by what they show
15
+ - Create visual search for products
16
+ - Compare images without AI APIs
17
+
18
+ ```javascript
19
+ import { init, CnnEmbedder } from '@ruvector/cnn';
20
+
21
+ await init();
22
+ const embedder = new CnnEmbedder();
23
+
24
+ // Turn an image into numbers
25
+ const numbers = embedder.extract(imagePixels, 224, 224);
26
+
27
+ // Compare two images (1.0 = identical, 0 = unrelated)
28
+ const similarity = embedder.cosineSimilarity(numbers1, numbers2);
29
+ ```
30
+
31
+ ## Why Use This?
32
+
33
+ | What You Get | Why It Matters |
34
+ |--------------|----------------|
35
+ | Runs in the browser | No server costs, instant results |
36
+ | ~5ms per image | Fast enough for real-time |
37
+ | ~900KB download | Small enough for any website |
38
+ | No API calls | Works offline, no per-image fees |
39
+ | Training included | Teach it your own categories |
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install @ruvector/cnn
45
+ ```
46
+
47
+ ## How to Use It
48
+
49
+ ### 1. Extract Image Features
50
+
51
+ ```javascript
52
+ import { init, CnnEmbedder } from '@ruvector/cnn';
53
+
54
+ // Start the engine (do this once)
55
+ await init();
56
+
57
+ // Create the feature extractor
58
+ const embedder = new CnnEmbedder({
59
+ embeddingDim: 512, // How many numbers per image
60
+ normalize: true // Make comparisons easier
61
+ });
62
+
63
+ // Get pixels from an image (RGB, no transparency)
64
+ // Each pixel has 3 values: red, green, blue (0-255)
65
+ const pixels = new Uint8Array(224 * 224 * 3);
66
+
67
+ // Turn pixels into 512 numbers that describe the image
68
+ const features = embedder.extract(pixels, 224, 224);
69
+ console.log('Got', features.length, 'numbers'); // 512
70
+ ```
71
+
72
+ ### 2. Compare Two Images
73
+
74
+ ```javascript
75
+ const features1 = embedder.extract(image1Pixels, 224, 224);
76
+ const features2 = embedder.extract(image2Pixels, 224, 224);
77
+
78
+ // How similar are they? (1.0 = same, 0 = different, -1 = opposite)
79
+ const score = embedder.cosineSimilarity(features1, features2);
80
+
81
+ if (score > 0.8) {
82
+ console.log('These images are very similar!');
83
+ } else if (score > 0.5) {
84
+ console.log('These images have some things in common');
85
+ } else {
86
+ console.log('These images are different');
87
+ }
88
+ ```
89
+
90
+ ### 3. Find the Most Similar Image
91
+
92
+ ```javascript
93
+ // Your collection of images (already converted to features)
94
+ const catalog = [
95
+ { name: 'red-shoe.jpg', features: embedder.extract(redShoePixels, 224, 224) },
96
+ { name: 'blue-bag.jpg', features: embedder.extract(blueBagPixels, 224, 224) },
97
+ { name: 'red-dress.jpg', features: embedder.extract(redDressPixels, 224, 224) },
98
+ ];
99
+
100
+ // User uploads a photo
101
+ const userPhoto = embedder.extract(uploadedPixels, 224, 224);
102
+
103
+ // Find the best match
104
+ let bestMatch = null;
105
+ let bestScore = -1;
106
+
107
+ for (const item of catalog) {
108
+ const score = embedder.cosineSimilarity(userPhoto, item.features);
109
+ if (score > bestScore) {
110
+ bestScore = score;
111
+ bestMatch = item.name;
112
+ }
113
+ }
114
+
115
+ console.log('Best match:', bestMatch, 'Score:', bestScore);
116
+ ```
117
+
118
+ ### 4. Get Pixels from a Canvas
119
+
120
+ ```javascript
121
+ // If you have an image in a canvas element
122
+ const canvas = document.getElementById('myCanvas');
123
+ const ctx = canvas.getContext('2d');
124
+
125
+ // Get the pixel data
126
+ const imageData = ctx.getImageData(0, 0, 224, 224);
127
+
128
+ // Canvas gives RGBA (4 values per pixel), we need RGB (3 values)
129
+ const rgb = new Uint8Array(224 * 224 * 3);
130
+ for (let i = 0, j = 0; i < imageData.data.length; i += 4, j += 3) {
131
+ rgb[j] = imageData.data[i]; // Red
132
+ rgb[j + 1] = imageData.data[i + 1]; // Green
133
+ rgb[j + 2] = imageData.data[i + 2]; // Blue
134
+ // Skip alpha (imageData.data[i + 3])
135
+ }
136
+
137
+ const features = embedder.extract(rgb, 224, 224);
138
+ ```
139
+
140
+ ## Training (Teaching It Your Categories)
141
+
142
+ You can train the model to be better at recognizing your specific images.
143
+
144
+ ### Contrastive Training (SimCLR style)
145
+
146
+ Show it pairs of images that should match:
147
+
148
+ ```javascript
149
+ import { init, InfoNCELoss, CnnEmbedder } from '@ruvector/cnn';
150
+
151
+ await init();
152
+
153
+ const embedder = new CnnEmbedder();
154
+ const trainer = new InfoNCELoss(0.1); // Lower = stricter matching
155
+
156
+ // Get features for your training pairs
157
+ // Pairs: image1 and image1_different_angle should match
158
+ const image1 = embedder.extract(photo1, 224, 224);
159
+ const image1_alt = embedder.extract(photo1_rotated, 224, 224);
160
+
161
+ // Pack into one array: [view1s..., view2s...]
162
+ const batch = new Float32Array(2 * 512);
163
+ batch.set(image1, 0);
164
+ batch.set(image1_alt, 512);
165
+
166
+ const loss = trainer.forward(batch, 1, 512);
167
+ console.log('Loss:', loss); // Lower is better
168
+ ```
169
+
170
+ ### Triplet Training
171
+
172
+ Show it: "A is similar to B, but different from C"
173
+
174
+ ```javascript
175
+ import { init, TripletLoss, CnnEmbedder } from '@ruvector/cnn';
176
+
177
+ await init();
178
+
179
+ const embedder = new CnnEmbedder();
180
+ const trainer = new TripletLoss(1.0); // margin
181
+
182
+ // Anchor: the reference image
183
+ // Positive: should be similar to anchor
184
+ // Negative: should be different from anchor
185
+ const anchor = embedder.extract(redShoePhoto, 224, 224);
186
+ const positive = embedder.extract(redShoePhoto2, 224, 224); // Same shoe
187
+ const negative = embedder.extract(blueBagPhoto, 224, 224); // Different item
188
+
189
+ const loss = trainer.forward(
190
+ new Float32Array(anchor),
191
+ new Float32Array(positive),
192
+ new Float32Array(negative),
193
+ 512
194
+ );
195
+ console.log('Loss:', loss);
196
+ ```
197
+
198
+ ## Fast Math Operations
199
+
200
+ If you're building custom features, these are optimized:
201
+
202
+ ```javascript
203
+ import { init, SimdOps, LayerOps } from '@ruvector/cnn';
204
+
205
+ await init();
206
+
207
+ // Dot product (sum of element-wise multiplication)
208
+ const a = new Float32Array([1, 2, 3, 4]);
209
+ const b = new Float32Array([5, 6, 7, 8]);
210
+ const result = SimdOps.dotProduct(a, b); // 70
211
+
212
+ // ReLU: set negative values to 0
213
+ const data = new Float32Array([-1, 0, 1, 7]);
214
+ SimdOps.relu(data); // [0, 0, 1, 7]
215
+
216
+ // ReLU6: clamp between 0 and 6
217
+ SimdOps.relu6(data); // [0, 0, 1, 6]
218
+
219
+ // L2 normalize (make length = 1)
220
+ SimdOps.l2Normalize(data);
221
+ ```
222
+
223
+ ## Performance
224
+
225
+ | What | How Long | Notes |
226
+ |------|----------|-------|
227
+ | Extract features (224×224 image) | ~5ms | With SIMD |
228
+ | Compare two images | ~0.01ms | Just math |
229
+ | Training step | ~1ms | Per batch |
230
+ | First load | ~100ms | Downloads WASM |
231
+
232
+ ## Browser Support
233
+
234
+ Works in all modern browsers with WebAssembly:
235
+ - Chrome 57+
236
+ - Firefox 52+
237
+ - Safari 11+
238
+ - Edge 16+
239
+
240
+ For best speed, use browsers with SIMD128 support:
241
+ - Chrome 91+
242
+ - Firefox 89+
243
+ - Safari 16.4+
244
+
245
+ ## Troubleshooting
246
+
247
+ **"init() takes too long"**
248
+ - Normal: First call downloads ~900KB WASM file
249
+ - Fix: Call init() early, before user needs results
250
+
251
+ **"Images look wrong"**
252
+ - Check: Images must be 224×224 pixels
253
+ - Check: Pixel format is RGB (3 values per pixel, not RGBA)
254
+ - Check: Values are 0-255, not 0-1
255
+
256
+ **"Similarity scores are all low"**
257
+ - Try: Set `normalize: true` in CnnEmbedder options
258
+ - Check: Are your images actually similar?
259
+
260
+ ## API Reference
261
+
262
+ ### CnnEmbedder
263
+
264
+ ```typescript
265
+ new CnnEmbedder(options?: {
266
+ embeddingDim?: number; // Default: 512
267
+ normalize?: boolean; // Default: true
268
+ })
269
+
270
+ .extract(pixels: Uint8Array, width: number, height: number): Float32Array
271
+ .cosineSimilarity(a: Float32Array, b: Float32Array): number
272
+ .embeddingDim: number
273
+ ```
274
+
275
+ ### InfoNCELoss
276
+
277
+ ```typescript
278
+ new InfoNCELoss(temperature?: number) // Default: 0.1
279
+
280
+ .forward(embeddings: Float32Array, batchSize: number, dim: number): number
281
+ .temperature: number
282
+ ```
283
+
284
+ ### TripletLoss
285
+
286
+ ```typescript
287
+ new TripletLoss(margin?: number) // Default: 1.0
288
+
289
+ .forward(anchors, positives, negatives: Float32Array, dim: number): number
290
+ .margin: number
291
+ ```
292
+
293
+ ## Related Packages
294
+
295
+ - [`ruvector`](https://www.npmjs.com/package/ruvector) — Core vector database
296
+ - [`@ruvector/attention`](https://www.npmjs.com/package/@ruvector/attention) — AI attention layers
297
+ - [`@ruvector/gnn`](https://www.npmjs.com/package/@ruvector/gnn) — Graph neural networks
298
+
299
+ ## License
300
+
301
+ MIT OR Apache-2.0
package/index.d.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @ruvector/cnn - CNN feature extraction for image embeddings
3
+ */
4
+
5
+ /**
6
+ * Initialize the WASM module
7
+ */
8
+ export function init(): Promise<void>;
9
+
10
+ /**
11
+ * Configuration for CNN embedder
12
+ */
13
+ export interface EmbedderConfig {
14
+ /** Input image size (square), default: 224 */
15
+ inputSize?: number;
16
+ /** Output embedding dimension, default: 512 */
17
+ embeddingDim?: number;
18
+ /** L2 normalize embeddings, default: true */
19
+ normalize?: boolean;
20
+ }
21
+
22
+ /**
23
+ * CNN Embedder for extracting image features
24
+ */
25
+ export class CnnEmbedder {
26
+ /**
27
+ * Create a new CNN embedder
28
+ * @param config - Configuration options
29
+ */
30
+ constructor(config?: EmbedderConfig);
31
+
32
+ /**
33
+ * Extract embedding from image data
34
+ * @param imageData - RGB image data (row-major, no alpha)
35
+ * @param width - Image width
36
+ * @param height - Image height
37
+ * @returns Embedding vector
38
+ */
39
+ extract(imageData: Uint8Array, width: number, height: number): Float32Array;
40
+
41
+ /**
42
+ * Compute cosine similarity between two embeddings
43
+ * @param a - First embedding
44
+ * @param b - Second embedding
45
+ * @returns Similarity in [-1, 1]
46
+ */
47
+ cosineSimilarity(a: Float32Array, b: Float32Array): number;
48
+
49
+ /**
50
+ * Get the embedding dimension
51
+ */
52
+ readonly embeddingDim: number;
53
+ }
54
+
55
+ /**
56
+ * InfoNCE loss for contrastive learning (SimCLR style)
57
+ */
58
+ export class InfoNCELoss {
59
+ /**
60
+ * Create InfoNCE loss
61
+ * @param temperature - Temperature parameter (default: 0.1)
62
+ */
63
+ constructor(temperature?: number);
64
+
65
+ /**
66
+ * Compute loss for embedding pairs
67
+ * @param embeddings - Flattened [2N, D] array where (i, i+N) are positive pairs
68
+ * @param batchSize - N (number of pairs)
69
+ * @param dim - D (embedding dimension)
70
+ * @returns Loss value
71
+ */
72
+ forward(embeddings: Float32Array, batchSize: number, dim: number): number;
73
+
74
+ /**
75
+ * Get temperature parameter
76
+ */
77
+ readonly temperature: number;
78
+ }
79
+
80
+ /**
81
+ * Triplet loss for metric learning
82
+ */
83
+ export class TripletLoss {
84
+ /**
85
+ * Create triplet loss
86
+ * @param margin - Margin parameter (default: 1.0)
87
+ */
88
+ constructor(margin?: number);
89
+
90
+ /**
91
+ * Compute triplet loss
92
+ * @param anchors - Anchor embeddings [N, D]
93
+ * @param positives - Positive embeddings [N, D]
94
+ * @param negatives - Negative embeddings [N, D]
95
+ * @param dim - Embedding dimension D
96
+ * @returns Loss value
97
+ */
98
+ forward(
99
+ anchors: Float32Array,
100
+ positives: Float32Array,
101
+ negatives: Float32Array,
102
+ dim: number
103
+ ): number;
104
+
105
+ /**
106
+ * Get margin parameter
107
+ */
108
+ readonly margin: number;
109
+ }
110
+
111
+ /**
112
+ * SIMD-optimized operations
113
+ */
114
+ export namespace SimdOps {
115
+ /**
116
+ * Compute dot product of two vectors
117
+ */
118
+ function dotProduct(a: Float32Array, b: Float32Array): number;
119
+
120
+ /**
121
+ * Apply ReLU activation in-place
122
+ */
123
+ function relu(data: Float32Array): void;
124
+
125
+ /**
126
+ * Apply ReLU6 activation in-place
127
+ */
128
+ function relu6(data: Float32Array): void;
129
+
130
+ /**
131
+ * L2 normalize a vector in-place
132
+ */
133
+ function l2Normalize(data: Float32Array): void;
134
+ }
135
+
136
+ /**
137
+ * Layer operations for building custom networks
138
+ */
139
+ export namespace LayerOps {
140
+ /**
141
+ * Apply batch normalization
142
+ */
143
+ function batchNorm(
144
+ input: Float32Array,
145
+ gamma: Float32Array,
146
+ beta: Float32Array,
147
+ mean: Float32Array,
148
+ variance: Float32Array,
149
+ epsilon?: number
150
+ ): void;
151
+
152
+ /**
153
+ * Apply global average pooling
154
+ * @param input - Input tensor [C, H, W]
155
+ * @param channels - Number of channels
156
+ * @param spatialSize - H * W
157
+ * @returns Output [C]
158
+ */
159
+ function globalAvgPool(
160
+ input: Float32Array,
161
+ channels: number,
162
+ spatialSize: number
163
+ ): Float32Array;
164
+ }