add caching model

This commit is contained in:
mphstar 2025-03-12 00:04:45 +07:00
parent 957a63ca7c
commit c7f37f589e
7 changed files with 150 additions and 50 deletions

View File

@ -2,12 +2,14 @@ import LayoutPage from "@/components/templates/LayoutPage";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { FaCircleCheck } from "react-icons/fa6"; import { FaCircleCheck } from "react-icons/fa6";
import * as tf from "@tensorflow/tfjs"; import * as tf from "@tensorflow/tfjs";
import { FilesetResolver, HandLandmarker } from "@mediapipe/tasks-vision"; import { HandLandmarker } from "@mediapipe/tasks-vision";
import calcLandmarkList from "@/utils/CalculateLandmark"; import calcLandmarkList from "@/utils/CalculateLandmark";
import preProcessLandmark from "@/utils/PreProcessLandmark"; import preProcessLandmark from "@/utils/PreProcessLandmark";
import ConvertResult from "@/utils/ConvertResult"; import ConvertResult from "@/utils/ConvertResult";
import useNavbarStore from "@/stores/NavbarStore"; import useNavbarStore from "@/stores/NavbarStore";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { loadTensorFlowModel } from "@/utils/tensorflowModelLoader";
import { loadHandLandmarker } from "@/utils/handLandmarkerLoader";
type PredictResult = { type PredictResult = {
abjad: String; abjad: String;
@ -50,12 +52,13 @@ const Home = () => {
const loadModel = async () => { const loadModel = async () => {
setLoadCamera(false); setLoadCamera(false);
try { try {
const lm = await tf.loadLayersModel("/model/model.json"); const lm = await loadTensorFlowModel();
model = lm; model = lm;
const emptyInput = tf.tensor2d([[0, 0]]); // const emptyInput = tf.tensor2d([[0, 0]]);
model.predict(emptyInput) as tf.Tensor; // model.predict(emptyInput) as tf.Tensor;
setLoadCamera(true); setLoadCamera(true);
} catch (error) { } catch (error) {
@ -65,17 +68,8 @@ const Home = () => {
const initializeHandDetection = async () => { const initializeHandDetection = async () => {
try { try {
const vision = await FilesetResolver.forVisionTasks( handLandmarker = await loadHandLandmarker();
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
);
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`,
},
numHands: 2,
runningMode: "VIDEO",
});
detectHands(); detectHands();
} catch (error) { } catch (error) {
console.error("Error initializing hand detection:", error); console.error("Error initializing hand detection:", error);
@ -117,11 +111,13 @@ const Home = () => {
performance.now() performance.now()
); );
setHandPresence(detections.handedness.length > 0); setHandPresence(detections.handedness.length > 0);
// Assuming detections.landmarks is an array of landmark objects // Assuming detections.landmarks is an array of landmark objects
if (detections.landmarks) { if (detections.landmarks) {
if (detections.handednesses.length > 0) { if (detections.handednesses.length > 0) {
console.log(detections); // console.log(detections);
if (detections.handednesses[0][0].displayName === "Right") { if (detections.handednesses[0][0].displayName === "Right") {
const landm = detections.landmarks[0].map((landmark) => landmark); const landm = detections.landmarks[0].map((landmark) => landmark);
@ -184,10 +180,7 @@ const Home = () => {
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path> ></path>
</svg> </svg>
<p className="ml-2"> <p className="ml-2">Gunakan tangan kanan. Pencahayaan ideal 300 Lux, jarak maksimal 2 meter dari kamera.</p>
Gunakan tangan kanan dan pastikan gambar pada kamera terlihat
jelas
</p>
</div> </div>
<button <button
onClick={() => setInfo(false)} onClick={() => setInfo(false)}

View File

@ -80,6 +80,12 @@ const Kamus = () => {
<div className="flex flex-col md:flex-row gap-6 overflow-y-auto flex-1"> <div className="flex flex-col md:flex-row gap-6 overflow-y-auto flex-1">
<div className="flex flex-col order-2 md:order-1"> <div className="flex flex-col order-2 md:order-1">
<p>{selectedKamus?.keterangan}</p> <p>{selectedKamus?.keterangan}</p>
<a href="/">
<button className="btn mt-8 bg-slate-900 text-white hover:bg-slate-950 w-fit">
Coba Sekarang
</button>
</a>
{/* <div className="flex flex-wrap gap-2 mt-3"> {/* <div className="flex flex-wrap gap-2 mt-3">
{selectedKamus?.badge.map((item, index) => ( {selectedKamus?.badge.map((item, index) => (
<span <span

View File

@ -1,7 +1,7 @@
import LayoutPage from "@/components/templates/LayoutPage"; import LayoutPage from "@/components/templates/LayoutPage";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import * as tf from "@tensorflow/tfjs"; import * as tf from "@tensorflow/tfjs";
import { FilesetResolver, HandLandmarker } from "@mediapipe/tasks-vision"; import { HandLandmarker } from "@mediapipe/tasks-vision";
import calcLandmarkList from "@/utils/CalculateLandmark"; import calcLandmarkList from "@/utils/CalculateLandmark";
import preProcessLandmark from "@/utils/PreProcessLandmark"; import preProcessLandmark from "@/utils/PreProcessLandmark";
import { abjads } from "@/utils/ConvertResult"; import { abjads } from "@/utils/ConvertResult";
@ -10,6 +10,8 @@ import { MdOutlineQuiz } from "react-icons/md";
import useMenyusunHurufStore from "@/stores/MenyusunHurufStore"; import useMenyusunHurufStore from "@/stores/MenyusunHurufStore";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { loadTensorFlowModel } from "@/utils/tensorflowModelLoader";
import { loadHandLandmarker } from "@/utils/handLandmarkerLoader";
// type PredictResult = { // type PredictResult = {
// abjad: String; // abjad: String;
@ -61,7 +63,7 @@ const Quiz = () => {
const loadModel = async () => { const loadModel = async () => {
setLoadCamera(false); setLoadCamera(false);
try { try {
const lm = await tf.loadLayersModel("/model/model.json"); const lm = await loadTensorFlowModel();
model = lm; model = lm;
const emptyInput = tf.tensor2d([[0, 0]]); const emptyInput = tf.tensor2d([[0, 0]]);
@ -76,16 +78,7 @@ const Quiz = () => {
const initializeHandDetection = async () => { const initializeHandDetection = async () => {
try { try {
const vision = await FilesetResolver.forVisionTasks( handLandmarker = await loadHandLandmarker();
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
);
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`,
},
numHands: 2,
runningMode: "VIDEO",
});
detectHands(); detectHands();
} catch (error) { } catch (error) {

View File

@ -1,7 +1,7 @@
import LayoutPage from "@/components/templates/LayoutPage"; import LayoutPage from "@/components/templates/LayoutPage";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import * as tf from "@tensorflow/tfjs"; import * as tf from "@tensorflow/tfjs";
import { FilesetResolver, HandLandmarker } from "@mediapipe/tasks-vision"; import { HandLandmarker } from "@mediapipe/tasks-vision";
import calcLandmarkList from "@/utils/CalculateLandmark"; import calcLandmarkList from "@/utils/CalculateLandmark";
import preProcessLandmark from "@/utils/PreProcessLandmark"; import preProcessLandmark from "@/utils/PreProcessLandmark";
import ConvertResult, { abjads } from "@/utils/ConvertResult"; import ConvertResult, { abjads } from "@/utils/ConvertResult";
@ -11,6 +11,8 @@ import { MdOutlineQuiz } from "react-icons/md";
import useTebakHurufStore from "@/stores/TebakHurufStore"; import useTebakHurufStore from "@/stores/TebakHurufStore";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { loadTensorFlowModel } from "@/utils/tensorflowModelLoader";
import { loadHandLandmarker } from "@/utils/handLandmarkerLoader";
// type PredictResult = { // type PredictResult = {
// abjad: String; // abjad: String;
@ -63,7 +65,7 @@ const Quiz = () => {
const loadModel = async () => { const loadModel = async () => {
setLoadCamera(false); setLoadCamera(false);
try { try {
const lm = await tf.loadLayersModel("/model/model.json"); const lm = await loadTensorFlowModel();
model = lm; model = lm;
const emptyInput = tf.tensor2d([[0, 0]]); const emptyInput = tf.tensor2d([[0, 0]]);
@ -78,16 +80,7 @@ const Quiz = () => {
const initializeHandDetection = async () => { const initializeHandDetection = async () => {
try { try {
const vision = await FilesetResolver.forVisionTasks( handLandmarker = await loadHandLandmarker();
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
);
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`,
},
numHands: 2,
runningMode: "VIDEO",
});
detectHands(); detectHands();
} catch (error) { } catch (error) {
@ -161,11 +154,13 @@ const Quiz = () => {
setTimeout(() => { setTimeout(() => {
setShowAnswer(false); setShowAnswer(false);
isLoading = false; setTimeout(() => {
setProgress(0); isLoading = false;
noSoal++; setProgress(0);
previousResult = []; noSoal++;
quizStore.setSoalIndex(noSoal); previousResult = [];
quizStore.setSoalIndex(noSoal);
}, 500);
if (noSoal === quizStore.listSoal.length) { if (noSoal === quizStore.listSoal.length) {
quizStore.setSession(false); quizStore.setSession(false);
@ -231,7 +226,7 @@ const Quiz = () => {
const store = useNavbarStore(); const store = useNavbarStore();
const quizStore = useTebakHurufStore(); const quizStore = useTebakHurufStore();
console.log(quizStore.jawaban); // console.log(quizStore.jawaban);
useEffect(() => { useEffect(() => {
if (!quizStore.session) { if (!quizStore.session) {

View File

@ -0,0 +1,34 @@
import { FilesetResolver, HandLandmarker } from "@mediapipe/tasks-vision";
import { getHandLandmarkerModel, saveHandLandmarkerModel } from "./indexedDBHelper";
let handLandmarker: HandLandmarker | null = null;
export async function loadHandLandmarker(): Promise<HandLandmarker> {
if (handLandmarker) return handLandmarker; // Jika model sudah ada, langsung kembalikan
let modelBlob = await getHandLandmarkerModel();
if (!modelBlob) {
console.log("🔄 Model Hand Landmarker tidak ditemukan di cache, mengunduh...");
const response = await fetch(
"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"
);
modelBlob = await response.blob();
await saveHandLandmarkerModel(modelBlob);
} else {
console.log("✅ Model Hand Landmarker ditemukan di cache, menggunakan model lokal.");
}
const vision = await FilesetResolver.forVisionTasks(
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
);
const modelURL = URL.createObjectURL(modelBlob);
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: { modelAssetPath: modelURL },
numHands: 2,
runningMode: "VIDEO",
});
return handLandmarker;
}

View File

@ -0,0 +1,59 @@
import * as tf from "@tensorflow/tfjs";
const DB_NAME = "ModelCacheDB";
const STORE_NAME = "models";
const TENSORFLOW_MODEL_KEY = "tensorflow_model";
const HAND_LANDMARKER_MODEL_KEY = "hand_landmarker_model";
// Membuka IndexedDB
export function openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME);
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 🟢 **Simpan Model TensorFlow.js ke IndexedDB**
export async function saveTensorFlowModel(model: tf.LayersModel): Promise<void> {
await model.save(`indexeddb://${TENSORFLOW_MODEL_KEY}`);
}
// 🟢 **Ambil Model TensorFlow.js dari IndexedDB**
export async function getTensorFlowModel(): Promise<tf.LayersModel | null> {
try {
return await tf.loadLayersModel(`indexeddb://${TENSORFLOW_MODEL_KEY}`);
} catch (error) {
console.warn("⚠️ Model TensorFlow tidak ditemukan di cache:", error);
return null;
}
}
// 🟢 **Simpan Model Hand Landmarker ke IndexedDB**
export async function saveHandLandmarkerModel(blob: Blob): Promise<void> {
const db = await openDB();
const transaction = db.transaction(STORE_NAME, "readwrite");
const store = transaction.objectStore(STORE_NAME);
store.put(blob, HAND_LANDMARKER_MODEL_KEY);
}
// 🟢 **Ambil Model Hand Landmarker dari IndexedDB**
export async function getHandLandmarkerModel(): Promise<Blob | null> {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(STORE_NAME, "readonly");
const store = transaction.objectStore(STORE_NAME);
const request = store.get(HAND_LANDMARKER_MODEL_KEY);
request.onsuccess = () => resolve(request.result as Blob | null);
request.onerror = () => reject(request.error);
});
}

View File

@ -0,0 +1,20 @@
import * as tf from "@tensorflow/tfjs";
import { getTensorFlowModel, saveTensorFlowModel } from "./indexedDBHelper";
let model: tf.LayersModel | null = null;
export async function loadTensorFlowModel(): Promise<tf.LayersModel> {
if (model) return model; // Jika model sudah dimuat, langsung kembalikan
model = await getTensorFlowModel();
if (!model) {
console.log("🔄 Model TensorFlow tidak ditemukan di cache, mengunduh...");
model = await tf.loadLayersModel("/model/model.json");
await saveTensorFlowModel(model);
} else {
console.log("✅ Model TensorFlow ditemukan di cache, menggunakan model lokal.");
}
return model;
}