Update project
This commit is contained in:
0
lib/core/services/ExitWrapper.dart
Normal file
0
lib/core/services/ExitWrapper.dart
Normal file
86
lib/core/services/vector_service.dart
Normal file
86
lib/core/services/vector_service.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
/// Holds the two vector embeddings: one for the cropped product image,
|
||||
/// one for the OCR-extracted label text.
|
||||
class VectorEmbeddingResult {
|
||||
final List<double> imageVector;
|
||||
final List<double> textVector;
|
||||
|
||||
const VectorEmbeddingResult({
|
||||
required this.imageVector,
|
||||
required this.textVector,
|
||||
});
|
||||
}
|
||||
|
||||
/// Computes vector embeddings entirely on-device — no backend call.
|
||||
///
|
||||
/// NOTE: these are lightweight placeholder embeddings (a downsampled
|
||||
/// grayscale pixel vector for the image, a character-hashing vector for
|
||||
/// the text), NOT a trained model like CLIP. They're deterministic and
|
||||
/// good enough to wire up printing/plumbing now. Swap `_imageVector` /
|
||||
/// `_textVector` for a real on-device model (e.g. a TFLite feature
|
||||
/// extractor) or a backend call later without touching the call site.
|
||||
class VectorService {
|
||||
static const int _imageVectorSize = 64; // 8x8 downsampled grayscale
|
||||
static const int _textVectorSize = 32; // hashed char buckets
|
||||
|
||||
static Future<VectorEmbeddingResult> getEmbeddings({
|
||||
required String imagePath,
|
||||
required String labelText,
|
||||
}) async {
|
||||
final imageVector = await _imageVector(imagePath);
|
||||
final textVector = _textVector(labelText);
|
||||
|
||||
return VectorEmbeddingResult(
|
||||
imageVector: imageVector,
|
||||
textVector: textVector,
|
||||
);
|
||||
}
|
||||
|
||||
/// Downsamples the image to an 8x8 grayscale grid and flattens it into
|
||||
/// a normalized (0-1) vector of length [_imageVectorSize].
|
||||
static Future<List<double>> _imageVector(String imagePath) async {
|
||||
final bytes = await File(imagePath).readAsBytes();
|
||||
final decoded = img.decodeImage(bytes);
|
||||
if (decoded == null) {
|
||||
return List<double>.filled(_imageVectorSize, 0);
|
||||
}
|
||||
|
||||
final side = math.sqrt(_imageVectorSize).round(); // 8
|
||||
final resized = img.copyResize(decoded, width: side, height: side);
|
||||
final gray = img.grayscale(resized);
|
||||
|
||||
final vector = <double>[];
|
||||
for (var y = 0; y < side; y++) {
|
||||
for (var x = 0; x < side; x++) {
|
||||
final pixel = gray.getPixel(x, y);
|
||||
vector.add(pixel.r / 255.0); // grayscale => r == g == b
|
||||
}
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
/// Simple character-hashing bag-of-characters vector, normalized so
|
||||
/// values sum to 1 (empty text => all zeros).
|
||||
static List<double> _textVector(String text) {
|
||||
final vector = List<double>.filled(_textVectorSize, 0);
|
||||
final normalized = text.toLowerCase();
|
||||
if (normalized.isEmpty) return vector;
|
||||
|
||||
for (final rune in normalized.runes) {
|
||||
final bucket = rune % _textVectorSize;
|
||||
vector[bucket] += 1;
|
||||
}
|
||||
|
||||
final total = vector.fold<double>(0, (sum, v) => sum + v);
|
||||
if (total > 0) {
|
||||
for (var i = 0; i < vector.length; i++) {
|
||||
vector[i] = vector[i] / total;
|
||||
}
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user