Grapevine_Disease_Detection/VinEye/src/components/scanner/ConfidenceMeter.tsx
Yanis 086de7c05c feat(scanner,ml): real TFLite inference + preload + flip camera + analyzing skeleton
ML
- Reinstall react-native-fast-tflite + react-native-nitro-modules and
  register the fast-tflite Expo plugin in app.json
- Wire model.ts to the real native module: dynamic require + lazy
  loadTensorflowModel (cached), softmax/argmax on output, build Detection
  with the project 0-100 confidence convention. Falls back to mockDetection
  on any load/inference failure so the app never breaks.
- Align preprocessing input size to 256x256 to match the Python
  MobileNetV2 export.

Scanner UX
- Preload the TFLite model on Scanner mount to avoid the ~1-2s decode hit
  on first capture
- Add a flip-front/back camera control with a toast warning that the rear
  camera gives better results
- Show a full-screen analyzing skeleton overlay while inference runs
- Memoize ConfidenceMeter color into a single computed value

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:31:17 +02:00

58 lines
2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';
import { colors } from '@/theme/colors';
import { typography } from '@/theme/typography';
import { spacing } from '@/theme/spacing';
import { useTranslation } from 'react-i18next';
interface ConfidenceMeterProps {
confidence: number; // 0100
}
function getConfidenceColor(confidence: number): string {
if (confidence >= 70) return colors.success;
if (confidence >= 40) return colors.warning;
return colors.danger;
}
export function ConfidenceMeter({ confidence }: ConfidenceMeterProps) {
const { t } = useTranslation();
const animatedWidth = useSharedValue(0);
const barColor = getConfidenceColor(confidence);
useEffect(() => {
animatedWidth.value = withTiming(confidence / 100, { duration: 500 });
}, [confidence]);
const barStyle = useAnimatedStyle(() => ({
width: `${animatedWidth.value * 100}%`,
backgroundColor: barColor,
}));
return (
<View style={styles.container}>
<View style={styles.labelRow}>
<Text style={styles.label}>{t('scanner.confidence')}</Text>
<Text style={[styles.value, { color: barColor }]}>{confidence}%</Text>
</View>
<View style={styles.track}>
<Animated.View style={[styles.bar, barStyle]} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { width: '100%', paddingHorizontal: spacing.base, gap: spacing.xs },
labelRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
label: { fontSize: typography.fontSizes.sm, color: colors.surface, fontWeight: typography.fontWeights.medium, opacity: 0.9 },
value: { fontSize: typography.fontSizes.md, fontWeight: typography.fontWeights.bold },
track: { height: 6, backgroundColor: 'rgba(255,255,255,0.2)', borderRadius: 3, overflow: 'hidden' },
bar: { height: 6, borderRadius: 3 },
});