creek
The AI Image Editor of 2030git clone https://github.com/maydayv7/creek
lib/services/analyze/embedding.dart (3705B)
1import 'dart:convert';
2import 'dart:io';
3import 'package:flutter/foundation.dart';
4import 'package:onnxruntime/onnxruntime.dart';
5import 'package:creekui/utils/image_utils.dart';
6import 'package:creekui/utils/clip_image_processor.dart';
7
8class EmbeddingAnalyzerService {
9 OrtSession? _session;
10 List<List<double>>? _centroids;
11 List<String>? _classes;
12
13 Future<void> initialize({
14 required String modelPath,
15 required String jsonPath,
16 }) async {
17 if (_session != null) return;
18
19 try {
20 debugPrint("Initializing Embeddings (Dino/CLIP)...");
21
22 // Initialize FFI Env
23 OrtEnv.instance.init();
24
25 // 1. Load Model from passed PATH (No rootBundle!)
26 final sessionOptions = OrtSessionOptions();
27 _session = OrtSession.fromFile(File(modelPath), sessionOptions);
28 sessionOptions.release();
29
30 final jsonString = await File(jsonPath).readAsString();
31 final jsonData = json.decode(jsonString);
32
33 _classes = List<String>.from(jsonData['classes']);
34 _centroids =
35 (jsonData['centroids'] as List)
36 .map((e) => List<double>.from(e))
37 .toList();
38 } catch (e) {
39 debugPrint("Embeddings Init Error: $e");
40 }
41 }
42
43 Future<Map<String, dynamic>?> analyze(
44 String imagePath, {
45 String? modelPath,
46 String? jsonPath,
47 }) async {
48 // Safety check
49 if (modelPath == null || jsonPath == null) {
50 return {'success': false, 'scores': {}, 'error': 'Paths missing'};
51 }
52 await initialize(modelPath: modelPath, jsonPath: jsonPath);
53 if (_session == null || _centroids == null) {
54 return {'success': false, 'scores': {}, 'error': 'Init failed'};
55 }
56
57 OrtValueTensor? inputOrt;
58 OrtRunOptions? runOptions;
59 List<OrtValue?>? outputs;
60
61 try {
62 final float32Input = await ClipImageProcessor.preprocess(imagePath);
63 if (float32Input == null) {
64 return {'success': false, 'scores': {}, 'error': 'Image decode failed'};
65 }
66
67 // Create Tensor
68 // Note: ensure ClipImageProcessor returns a flat List<double>
69 inputOrt = OrtValueTensor.createTensorWithDataList(float32Input, [
70 1,
71 3,
72 224,
73 224,
74 ]);
75
76 runOptions = OrtRunOptions();
77
78 // Run Inference
79 // 'image' is the input name for CLIP.
80 outputs = _session!.run(runOptions, {"image": inputOrt});
81
82 if (outputs.isEmpty) throw Exception("No output from model");
83
84 // Get Output
85 // FFI returns list of outputs. Usually index 0.
86 final dynamic outputRaw = outputs[0]?.value;
87
88 // Flatten Output
89 final List<double> imgFeat = [];
90
91 void flatten(dynamic data) {
92 if (data is num) {
93 imgFeat.add(data.toDouble());
94 } else if (data is List) {
95 for (var item in data) {
96 flatten(item);
97 }
98 }
99 }
100
101 flatten(outputRaw);
102
103 // Normalize & Compare
104 final normFeat = l2Normalize(imgFeat);
105 Map<String, double> scores = {};
106
107 for (int i = 0; i < _classes!.length; i++) {
108 double score = dotProduct(normFeat, _centroids![i]);
109 scores[_classes![i]] = score * 100.0;
110 }
111
112 scores = Map.fromEntries(
113 (scores.entries.toList()
114 ..sort((a, b) => b.value.compareTo(a.value))), //.take(3)
115 );
116
117 return {"success": true, "scores": scores, "error": null};
118 } catch (e) {
119 debugPrint("Embeddings Analysis Error: $e");
120 return {'success': false, 'scores': {}, 'error': e.toString()};
121 } finally {
122 inputOrt?.release();
123 runOptions?.release();
124 outputs?.forEach((element) => element?.release());
125 }
126 }
127
128 void dispose() {
129 _session?.release();
130 }
131}