creek

The AI Image Editor of 2030

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}