chore(ml,android): retire react-native-fast-tflite + nitro, mock JS only
Contexte - Build Android C++ instable sur Windows (CMake/Ninja path too long, Nitro headers manquants au clean). Modèle .tflite final pas encore prêt. - Désinstall temporaire des deux libs natives, le mock JS dans model.ts continue de servir les détections simulées pondérées. Changements - package.json : retire react-native-fast-tflite (3.0.1) - pnpm-lock.yaml : régénéré, -72 packages dont nitro-modules - src/services/tflite/model.ts : refactor pur mock, interface publique inchangée (loadModel + runInference), procédure de réintégration documentée en tête du fichier - plugins/withCmakeFix.js : plugin Expo config qui injecte les flags CMake (response files + ninja 1.12.1 + OBJECT_PATH_MAX) à chaque prebuild — dormant tant que fast-tflite n'est pas réintégré - app.json : référence le plugin - CLAUDE.md + .claude/notes/android-build : doc de l'état actuel et des étapes de réintégration (idéalement via EAS Build) Reste - src/assets/models/grapevine_v1.tflite conservé pour la réintégration - metro.config.js continue de déclarer .tflite dans assetExts - TypeScript check: 1 erreur préexistante (homeheader.tsx, palette[50]), non liée à ce changement Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8b84472e6
commit
6232068208
|
|
@ -14,9 +14,21 @@
|
||||||
|
|
||||||
### Solution appliquée
|
### Solution appliquée
|
||||||
|
|
||||||
#### 1. `VinEye/android/app/build.gradle` — bloc `externalNativeBuild`
|
> ⚠️ `android/` est gitignored (régénéré par `expo prebuild`). On ne peut PAS éditer `android/app/build.gradle` à la main et le commiter — la modification serait perdue au prochain prebuild. La solution est un **plugin Expo config** (`plugins/withCmakeFix.js`) qui ré-injecte le bloc à chaque prebuild.
|
||||||
|
|
||||||
Ajouté dans `android.defaultConfig` :
|
#### 1. Plugin Expo config — `VinEye/plugins/withCmakeFix.js`
|
||||||
|
|
||||||
|
Plugin qui utilise `withAppBuildGradle` pour injecter le bloc `externalNativeBuild` dans `defaultConfig`. Idempotent grâce au marker `// CMAKE_FIX_INJECTED`.
|
||||||
|
|
||||||
|
Référencé dans `app.json` :
|
||||||
|
```json
|
||||||
|
"plugins": [
|
||||||
|
"./plugins/withCmakeFix",
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Bloc injecté dans `android/app/build.gradle` (généré)
|
||||||
|
|
||||||
```gradle
|
```gradle
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
|
@ -58,7 +70,17 @@ Pas besoin de télécharger — on pointe `CMAKE_MAKE_PROGRAM` directement dessu
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fix #2 — `react-native-nitro-modules` headers manquants (2026-04-30, en cours)
|
## Fix #2 — `react-native-nitro-modules` headers manquants (2026-04-30, ✅ contourné)
|
||||||
|
|
||||||
|
> **Décision finale** : `react-native-fast-tflite` et `react-native-nitro-modules` ont été **désinstallés**. Le mock JS dans `src/services/tflite/model.ts` continue de fournir des résultats simulés. Le `.tflite` reste dans `src/assets/models/` pour la réintégration future. Procédure de réintégration documentée en tête de `model.ts`.
|
||||||
|
|
||||||
|
### Pourquoi ce contournement
|
||||||
|
- Le modèle ML n'est pas encore prêt pour la prod (~30% précision sur dataset terrain)
|
||||||
|
- Les builds Android C++ Nitro/fast-tflite sont fragiles sur Windows
|
||||||
|
- Le mock TS suffit pour le développement UI/UX
|
||||||
|
- Quand le `.tflite` sera prêt → réintégrer via **EAS Build** pour éviter les builds locaux Windows
|
||||||
|
|
||||||
|
### Erreur historique (avant désinstall)
|
||||||
|
|
||||||
### Symptômes
|
### Symptômes
|
||||||
```
|
```
|
||||||
|
|
@ -79,7 +101,7 @@ CMake Error in CMakeLists.txt:
|
||||||
4. **Vérifier la version** — incompatibilité possible entre `react-native-fast-tflite` et `react-native-nitro-modules` (vérifier les peerDependencies)
|
4. **Vérifier la version** — incompatibilité possible entre `react-native-fast-tflite` et `react-native-nitro-modules` (vérifier les peerDependencies)
|
||||||
|
|
||||||
### Statut
|
### Statut
|
||||||
🟡 **En cours** — fix CMake/ninja passé, ce nouveau problème est sur la chaîne de dépendances Gradle.
|
✅ **Résolu par contournement** — fast-tflite désinstallé, mock JS en place, builds C++ Nitro plus nécessaires.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Cible des amateurs de vin/jardinage. Scan par camera, identification de maladies
|
||||||
| Styling | **NativeWind v4** (Tailwind) prioritaire, StyleSheet pour ombres/gradients |
|
| Styling | **NativeWind v4** (Tailwind) prioritaire, StyleSheet pour ombres/gradients |
|
||||||
| Icones | **lucide-react-native** (bottom bar) + **Ionicons** (reste de l'app) |
|
| Icones | **lucide-react-native** (bottom bar) + **Ionicons** (reste de l'app) |
|
||||||
| Animations | React Native Reanimated v4 |
|
| Animations | React Native Reanimated v4 |
|
||||||
| IA | **react-native-fast-tflite** + MobileNetV2 (.tflite, 9.4 MB, 4 classes) |
|
| IA | Mock JS pondéré (random 4 classes) — `react-native-fast-tflite` désinstallé temporairement, voir `services/tflite/model.ts` pour la procédure de réintégration |
|
||||||
| Persistance | AsyncStorage |
|
| Persistance | AsyncStorage |
|
||||||
| i18n | i18next + react-i18next (FR + EN) |
|
| i18n | i18next + react-i18next (FR + EN) |
|
||||||
| Camera | expo-camera |
|
| Camera | expo-camera |
|
||||||
|
|
@ -193,8 +193,11 @@ pnpm ios # Build iOS
|
||||||
|
|
||||||
## ML / inference on-device
|
## ML / inference on-device
|
||||||
|
|
||||||
Le modele MobileNetV2 (val_accuracy 99.93% — voir `docs/paper.md`) est embarque
|
> ⚠️ **2026-04-30** : `react-native-fast-tflite` et `react-native-nitro-modules` ont été **désinstallés temporairement**. Le service `services/tflite/model.ts` retourne actuellement un **mock JS pondéré** (random sur les 4 classes). Raisons : modèle pas encore exporté en `.tflite` final + builds Android C++ instables sur Windows (CMake/Nitro headers). Procédure de réintégration documentée en tête de `services/tflite/model.ts`.
|
||||||
dans le bundle et execute en local via `react-native-fast-tflite`.
|
|
||||||
|
Le modele MobileNetV2 (val_accuracy 99.93% — voir `docs/paper.md`) est destiné
|
||||||
|
à être embarqué dans le bundle et exécuté en local via `react-native-fast-tflite`
|
||||||
|
une fois la lib réintégrée.
|
||||||
|
|
||||||
### Pipeline
|
### Pipeline
|
||||||
|
|
||||||
|
|
@ -270,5 +273,11 @@ le dev sans device natif.
|
||||||
|
|
||||||
Détail complet : [`.claude/notes/android-build/README.md`](.claude/notes/android-build/README.md)
|
Détail complet : [`.claude/notes/android-build/README.md`](.claude/notes/android-build/README.md)
|
||||||
|
|
||||||
- ✅ **CMake/Ninja path too long** — résolu via `externalNativeBuild.cmake.arguments` dans `android/app/build.gradle` (response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024`)
|
- ✅ **CMake/Ninja path too long** — résolu via plugin Expo config `plugins/withCmakeFix.js` (référencé dans `app.json`) qui injecte response files + ninja 1.12.1 + `CMAKE_OBJECT_PATH_MAX=1024` à chaque prebuild
|
||||||
- 🟡 **`react-native-nitro-modules` headers manquants** — survient au clean ; corriger en buildant Nitro avant fast-tflite, ou via `pnpm dlx expo prebuild --clean`
|
- ✅ **`react-native-nitro-modules` headers manquants** — contourné en désinstallant `react-native-fast-tflite` (qui dépendait de Nitro). Mock JS en place. À réintégrer quand le `.tflite` sera prêt et idéalement via EAS Build pour éviter les soucis Windows.
|
||||||
|
|
||||||
|
### Setup dev Windows recommandé
|
||||||
|
|
||||||
|
- **Chemin court** : placer le projet dans `C:\dev\vineye\` plutôt que `C:\Users\Client\projet_web\...\VinEye\` — réduit ~50 chars sur tous les chemins de build CMake
|
||||||
|
- **`LongPathsEnabled` registre** : `HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled = 1` (déjà actif sur ce poste)
|
||||||
|
- **Git long paths** : `git config --system core.longpaths true` (en PowerShell admin)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
"favicon": "./src/assets/images/icon.png"
|
"favicon": "./src/assets/images/icon.png"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"./plugins/withCmakeFix",
|
||||||
"expo-localization",
|
"expo-localization",
|
||||||
[
|
[
|
||||||
"expo-camera",
|
"expo-camera",
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@
|
||||||
"react-i18next": "^17.0.1",
|
"react-i18next": "^17.0.1",
|
||||||
"react-lucid": "^0.0.1",
|
"react-lucid": "^0.0.1",
|
||||||
"react-native": "0.81.5",
|
"react-native": "0.81.5",
|
||||||
"react-native-fast-tflite": "^3.0.1",
|
|
||||||
"react-native-gesture-handler": "~2.28.0",
|
"react-native-gesture-handler": "~2.28.0",
|
||||||
"react-native-reanimated": "~4.1.1",
|
"react-native-reanimated": "~4.1.1",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
|
|
|
||||||
42
VinEye/plugins/withCmakeFix.js
Normal file
42
VinEye/plugins/withCmakeFix.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
const { withAppBuildGradle } = require("expo/config-plugins");
|
||||||
|
|
||||||
|
const NINJA_PATH =
|
||||||
|
"C:\\\\Users\\\\Client\\\\AppData\\\\Local\\\\Android\\\\Sdk\\\\cmake\\\\4.1.2\\\\bin\\\\ninja.exe";
|
||||||
|
|
||||||
|
const CMAKE_BLOCK = `
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DCMAKE_MAKE_PROGRAM=${NINJA_PATH}",
|
||||||
|
"-DCMAKE_OBJECT_PATH_MAX=1024",
|
||||||
|
"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS=1",
|
||||||
|
"-DCMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES=1",
|
||||||
|
"-DCMAKE_CXX_RESPONSE_FILE_LINK_FLAG=@",
|
||||||
|
"-DCMAKE_NINJA_FORCE_RESPONSE_FILE=1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MARKER = "// CMAKE_FIX_INJECTED";
|
||||||
|
|
||||||
|
function injectCmakeFix(buildGradle) {
|
||||||
|
if (buildGradle.includes(MARKER)) return buildGradle;
|
||||||
|
|
||||||
|
const defaultConfigRegex = /(defaultConfig\s*\{)([\s\S]*?)(\n\s*\})/m;
|
||||||
|
const match = buildGradle.match(defaultConfigRegex);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(
|
||||||
|
"[withCmakeFix] Impossible de trouver le bloc defaultConfig dans build.gradle"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, openTag, body, closeTag] = match;
|
||||||
|
const newBlock = `${openTag}${body}\n ${MARKER}${CMAKE_BLOCK}${closeTag}`;
|
||||||
|
return buildGradle.replace(defaultConfigRegex, newBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function withCmakeFix(config) {
|
||||||
|
return withAppBuildGradle(config, (config) => {
|
||||||
|
config.modResults.contents = injectCmakeFix(config.modResults.contents);
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -113,9 +113,6 @@ importers:
|
||||||
react-native:
|
react-native:
|
||||||
specifier: 0.81.5
|
specifier: 0.81.5
|
||||||
version: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
version: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||||
react-native-fast-tflite:
|
|
||||||
specifier: ^3.0.1
|
|
||||||
version: 3.0.1(react-native-nitro-modules@0.35.6(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
|
||||||
react-native-gesture-handler:
|
react-native-gesture-handler:
|
||||||
specifier: ~2.28.0
|
specifier: ~2.28.0
|
||||||
version: 2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
version: 2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||||
|
|
@ -2948,14 +2945,6 @@ packages:
|
||||||
react-native-svg:
|
react-native-svg:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
react-native-fast-tflite@3.0.1:
|
|
||||||
resolution: {integrity: sha512-88wNR/4iR8X0zuQtrpb1jRbF+X+hUqrD8cER4DhNJnbhA+3PuGz8SoP3n8WEhjYWDkGqTme2Ezk+mbeLiiE+6w==}
|
|
||||||
engines: {node: '>= 18'}
|
|
||||||
peerDependencies:
|
|
||||||
react: '*'
|
|
||||||
react-native: '*'
|
|
||||||
react-native-nitro-modules: '*'
|
|
||||||
|
|
||||||
react-native-gesture-handler@2.28.0:
|
react-native-gesture-handler@2.28.0:
|
||||||
resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==}
|
resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -2968,12 +2957,6 @@ packages:
|
||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
react-native-nitro-modules@0.35.6:
|
|
||||||
resolution: {integrity: sha512-3Cb7s+O5tpZ6RdIiPOB/wi3IMfBxD6tl6VDF8gJ5zvM/BEGTWxwMMLjzmWmsYPKekdbYBznF6qp2d2SxixPy8g==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '*'
|
|
||||||
react-native: '*'
|
|
||||||
|
|
||||||
react-native-reanimated@4.1.7:
|
react-native-reanimated@4.1.7:
|
||||||
resolution: {integrity: sha512-Q4H6xA3Tn7QL0/E/KjI86I1KK4tcf+ErRE04LH34Etka2oVQhW6oXQ+Q8ZcDCVxiWp5vgbBH6XcH8BOo4w/Rhg==}
|
resolution: {integrity: sha512-Q4H6xA3Tn7QL0/E/KjI86I1KK4tcf+ErRE04LH34Etka2oVQhW6oXQ+Q8ZcDCVxiWp5vgbBH6XcH8BOo4w/Rhg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -4846,7 +4829,9 @@ snapshots:
|
||||||
metro-runtime: 0.83.5
|
metro-runtime: 0.83.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@react-native/normalize-colors@0.74.89': {}
|
'@react-native/normalize-colors@0.74.89': {}
|
||||||
|
|
@ -6985,12 +6970,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
react-native-fast-tflite@3.0.1(react-native-nitro-modules@0.35.6(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
react: 19.1.0
|
|
||||||
react-native: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
|
||||||
react-native-nitro-modules: 0.35.6(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
|
||||||
|
|
||||||
react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@egjs/hammerjs': 2.0.17
|
'@egjs/hammerjs': 2.0.17
|
||||||
|
|
@ -7004,11 +6983,6 @@ snapshots:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-native: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
react-native: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
||||||
|
|
||||||
react-native-nitro-modules@0.35.6(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
react: 19.1.0
|
|
||||||
react-native: 0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0)
|
|
||||||
|
|
||||||
react-native-reanimated@4.1.7(react-native-worklets@0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
react-native-reanimated@4.1.7(react-native-worklets@0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,47 @@
|
||||||
|
/**
|
||||||
|
* MOCK TFLite Service
|
||||||
|
*
|
||||||
|
* Ce service retourne actuellement des résultats simulés (random pondéré).
|
||||||
|
* Les libs `react-native-fast-tflite` et `react-native-nitro-modules` ont été
|
||||||
|
* désinstallées temporairement car :
|
||||||
|
* - Le modèle ML n'est pas encore exporté en .tflite final (précision insuffisante)
|
||||||
|
* - Les builds Android C++ (CMake/Ninja + Nitro headers) étaient instables sur Windows
|
||||||
|
*
|
||||||
|
* L'interface publique reste identique :
|
||||||
|
* - `loadModel(): Promise<boolean>` — retourne false (pas de modèle chargé)
|
||||||
|
* - `runInference(imageUri?: string): Promise<Detection>` — renvoie un mock pondéré
|
||||||
|
*
|
||||||
|
* RÉINTÉGRATION DU VRAI MODÈLE (quand le .tflite sera prêt) :
|
||||||
|
* 1. pnpm add react-native-fast-tflite react-native-nitro-modules
|
||||||
|
* 2. Vérifier que `src/assets/models/grapevine_v1.tflite` est présent
|
||||||
|
* 3. Remplacer `runInference` ci-dessous par l'implémentation native :
|
||||||
|
* const tflite = require('react-native-fast-tflite');
|
||||||
|
* const asset = require('@/assets/models/grapevine_v1.tflite');
|
||||||
|
* const model = await tflite.loadTensorflowModel(asset);
|
||||||
|
* const input = await preprocessImage(imageUri); // depuis services/ml/preprocessing
|
||||||
|
* const outputs = model.runSync([input]);
|
||||||
|
* // ... softmax/argmax → buildDetection
|
||||||
|
* 4. pnpm dlx expo prebuild --clean
|
||||||
|
* 5. pnpm dlx expo run:android (ou EAS Build pour éviter les soucis CMake Windows)
|
||||||
|
*
|
||||||
|
* Documentation : https://github.com/mrousavy/react-native-fast-tflite
|
||||||
|
*/
|
||||||
|
|
||||||
import type { Detection, DiseaseClass, ClassProbability } from '@/types/detection';
|
import type { Detection, DiseaseClass, ClassProbability } from '@/types/detection';
|
||||||
import { ML_CLASSES, CLASS_TO_SLUG, CONFIDENCE_THRESHOLD_VINE, CONFIDENCE_THRESHOLD_UNCERTAIN } from '@/services/ml/classes';
|
import {
|
||||||
import { preprocessImage, argmax, softmax } from '@/services/ml/preprocessing';
|
ML_CLASSES,
|
||||||
|
CLASS_TO_SLUG,
|
||||||
type FastTfliteModel = {
|
CONFIDENCE_THRESHOLD_VINE,
|
||||||
runSync: (inputs: (Float32Array | Int32Array | Uint8Array)[]) => (Float32Array | Int32Array | Uint8Array)[];
|
CONFIDENCE_THRESHOLD_UNCERTAIN,
|
||||||
};
|
} from '@/services/ml/classes';
|
||||||
|
import { argmax } from '@/services/ml/preprocessing';
|
||||||
let cachedModel: FastTfliteModel | null = null;
|
|
||||||
let modelLoadFailed = false;
|
|
||||||
|
|
||||||
async function getModel(): Promise<FastTfliteModel | null> {
|
|
||||||
if (cachedModel) return cachedModel;
|
|
||||||
if (modelLoadFailed) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tflite = require('react-native-fast-tflite');
|
|
||||||
const asset = require('@/assets/models/grapevine_v1.tflite');
|
|
||||||
cachedModel = await tflite.loadTensorflowModel(asset);
|
|
||||||
return cachedModel;
|
|
||||||
} catch (err) {
|
|
||||||
if (__DEV__) {
|
|
||||||
console.warn('[TFLite] Failed to load model — falling back to mock:', err);
|
|
||||||
}
|
|
||||||
modelLoadFailed = true;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadModel(): Promise<boolean> {
|
export async function loadModel(): Promise<boolean> {
|
||||||
const m = await getModel();
|
return false;
|
||||||
return m !== null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runInference(imageUri?: string): Promise<Detection> {
|
export async function runInference(imageUri?: string): Promise<Detection> {
|
||||||
const timestamp = Date.now();
|
return mockDetection(Date.now(), imageUri);
|
||||||
|
|
||||||
if (!imageUri) {
|
|
||||||
return mockDetection(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = await getModel();
|
|
||||||
if (!model) {
|
|
||||||
return mockDetection(timestamp, imageUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const input = await preprocessImage(imageUri);
|
|
||||||
const outputs = model.runSync([input]);
|
|
||||||
const raw = outputs[0];
|
|
||||||
|
|
||||||
const rawArr = raw instanceof Float32Array ? Array.from(raw) : Array.from(raw as ArrayLike<number>);
|
|
||||||
const probs = isProbabilityVector(rawArr) ? rawArr : softmax(rawArr);
|
|
||||||
|
|
||||||
const idx = argmax(probs);
|
|
||||||
const topClass = ML_CLASSES[idx];
|
|
||||||
const topProb = probs[idx];
|
|
||||||
|
|
||||||
const allProbabilities: ClassProbability[] = ML_CLASSES.map((cls, i) => ({
|
|
||||||
class: cls,
|
|
||||||
probability: probs[i],
|
|
||||||
}));
|
|
||||||
|
|
||||||
return buildDetection({
|
|
||||||
timestamp,
|
|
||||||
imageUri,
|
|
||||||
topClass,
|
|
||||||
topProb,
|
|
||||||
allProbabilities,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
if (__DEV__) {
|
|
||||||
console.warn('[TFLite] Inference failed — falling back to mock:', err);
|
|
||||||
}
|
|
||||||
return mockDetection(timestamp, imageUri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDetection(args: {
|
function buildDetection(args: {
|
||||||
|
|
@ -104,13 +72,6 @@ function buildDetection(args: {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProbabilityVector(values: number[]): boolean {
|
|
||||||
if (values.length === 0) return false;
|
|
||||||
const sum = values.reduce((a, b) => a + b, 0);
|
|
||||||
if (Math.abs(sum - 1) > 0.05) return false;
|
|
||||||
return values.every((v) => v >= 0 && v <= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockDetection(timestamp: number, imageUri?: string): Detection {
|
function mockDetection(timestamp: number, imageUri?: string): Detection {
|
||||||
const probs = randomProbabilities();
|
const probs = randomProbabilities();
|
||||||
const idx = argmax(probs);
|
const idx = argmax(probs);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue