|
|
import gradio as gr |
|
|
import torch |
|
|
import torch.nn as nn |
|
|
from torchvision import models, transforms |
|
|
from PIL import Image |
|
|
import os |
|
|
import google.generativeai as genai |
|
|
import pandas as pd |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("Memuat semua model dan konfigurasi...") |
|
|
device = torch.device('cpu') |
|
|
|
|
|
|
|
|
try: |
|
|
df_referensi = pd.read_csv('link_referensi.csv') |
|
|
print("File 'link_referensi.csv' berhasil dimuat.") |
|
|
referensi_loaded = True |
|
|
except FileNotFoundError: |
|
|
print("PERINGATAN: File 'link_referensi.csv' tidak ditemukan. Fitur referensi resep lokal akan dinonaktifkan.") |
|
|
referensi_loaded = False |
|
|
|
|
|
|
|
|
try: |
|
|
genai.configure(api_key=os.environ['GEMINI_API_KEY']) |
|
|
gemini_model = genai.GenerativeModel("gemini-1.5-flash") |
|
|
gemini_enabled = True |
|
|
print("Koneksi ke Gemini API (gemini-1.5-flash) berhasil.") |
|
|
except KeyError: |
|
|
gemini_enabled = False |
|
|
print("PERINGATAN: GEMINI_API_KEY tidak ditemukan. Fitur deskripsi dinonaktifkan.") |
|
|
|
|
|
|
|
|
model_biner = models.mobilenet_v2(pretrained=False) |
|
|
model_biner.classifier[1] = nn.Linear(model_biner.last_channel, 1) |
|
|
model_biner.load_state_dict(torch.load('plant_detector_binary.pth', map_location=device)) |
|
|
model_biner.eval() |
|
|
print("Model deteksi biner (plant_detector_binary.pth) berhasil dimuat.") |
|
|
|
|
|
|
|
|
with open('class_names.txt', 'r') as f: |
|
|
class_names = [line.strip() for line in f.readlines()] |
|
|
num_classes = len(class_names) |
|
|
|
|
|
model_herbal = models.vgg11_bn(pretrained=False) |
|
|
num_ftrs = model_herbal.classifier[6].in_features |
|
|
model_herbal.classifier[6] = nn.Linear(num_ftrs, num_classes) |
|
|
model_herbal.load_state_dict(torch.load('herbal_vgg11_optimized.pth', map_location=device)) |
|
|
model_herbal.eval() |
|
|
print(f"Model deteksi herbal (herbal_vgg11_optimized.pth) dengan {num_classes} kelas berhasil dimuat.") |
|
|
|
|
|
|
|
|
transform = transforms.Compose([ |
|
|
transforms.Resize((224, 224)), |
|
|
transforms.ToTensor(), |
|
|
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) |
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_plant_info_from_gemini(plant_name): |
|
|
"""Meminta informasi tanaman dari Gemini (tanpa resep).""" |
|
|
if not gemini_enabled: |
|
|
return "Fitur deskripsi tidak aktif karena GEMINI_API_KEY tidak dikonfigurasi." |
|
|
try: |
|
|
|
|
|
prompt = f""" |
|
|
Berikan informasi mengenai tanaman herbal '{plant_name}' dalam Bahasa Indonesia. |
|
|
Tolong susun jawabannya dalam format yang jelas: |
|
|
|
|
|
**Deskripsi:** |
|
|
[Tulis deskripsi singkat tentang apa itu tanaman {plant_name}, asalnya, dan ciri-ciri utamanya di sini] |
|
|
|
|
|
**Manfaat:** |
|
|
[Sajikan dalam bentuk daftar poin (bullet points)] |
|
|
- [Manfaat 1] |
|
|
- [Manfaat 2] |
|
|
- [dan seterusnya...] |
|
|
|
|
|
**Cara Pengolahan Umum:** |
|
|
[Jelaskan cara pengolahan secara umum untuk penggunaan luar dan konsumsi.] |
|
|
""" |
|
|
response = gemini_model.generate_content(prompt) |
|
|
return response.text |
|
|
except Exception as e: |
|
|
return f"Gagal menghubungi Gemini: {e}" |
|
|
|
|
|
|
|
|
def get_links_from_csv(plant_name): |
|
|
"""Mencari link referensi dari file CSV yang sudah dimuat.""" |
|
|
if not referensi_loaded: |
|
|
return "File referensi lokal tidak dimuat." |
|
|
|
|
|
|
|
|
|
|
|
hasil = df_referensi[df_referensi['Nama Tanaman'].str.contains(plant_name, case=False, na=False)] |
|
|
|
|
|
if hasil.empty: |
|
|
return f"Tidak ditemukan referensi resep lokal untuk '{plant_name}'." |
|
|
|
|
|
|
|
|
links_markdown = "### Referensi Resep (dari file lokal):\n" |
|
|
for index, row in hasil.iterrows(): |
|
|
links_markdown += f"- [{row['URL']}]({row['URL']})\n" |
|
|
|
|
|
return links_markdown |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def predict_cascaded(image): |
|
|
"""Fungsi utama yang menjalankan semua tahap prediksi.""" |
|
|
if image is None: |
|
|
return None, "Tidak ada gambar.", "Silakan unggah gambar.", "Tidak ada data." |
|
|
|
|
|
image_pil = Image.fromarray(image.astype('uint8'), 'RGB') |
|
|
image_transformed = transform(image_pil).unsqueeze(0).to(device) |
|
|
|
|
|
|
|
|
with torch.no_grad(): |
|
|
output_biner = model_biner(image_transformed) |
|
|
prob_is_plant = torch.sigmoid(output_biner).item() |
|
|
|
|
|
DETECTION_THRESHOLD = 0.5 |
|
|
if prob_is_plant < DETECTION_THRESHOLD: |
|
|
return {"Bukan Tanaman": 1 - prob_is_plant}, "Gambar tidak terdeteksi sebagai tanaman.", "Silakan unggah gambar tanaman.", "Tidak ada data." |
|
|
|
|
|
|
|
|
with torch.no_grad(): |
|
|
outputs_herbal = model_herbal(image_transformed) |
|
|
probabilities = torch.nn.functional.softmax(outputs_herbal[0], dim=0) |
|
|
confidences = {class_names[i]: float(probabilities[i]) for i in range(num_classes)} |
|
|
|
|
|
|
|
|
top_prediction_full_name = max(confidences, key=confidences.get) |
|
|
|
|
|
top_prediction_name = top_prediction_full_name.split('(')[0].strip() |
|
|
|
|
|
|
|
|
plant_info_gemini = get_plant_info_from_gemini(top_prediction_name) |
|
|
plant_links_csv = get_links_from_csv(top_prediction_name) |
|
|
|
|
|
|
|
|
return confidences, top_prediction_full_name, plant_info_gemini, plant_links_csv |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface = gr.Interface( |
|
|
fn=predict_cascaded, |
|
|
inputs=gr.Image(label="Unggah Gambar Tanaman"), |
|
|
|
|
|
outputs=[ |
|
|
gr.Label(num_top_classes=5, label="Hasil Prediksi (Probabilitas)"), |
|
|
gr.Textbox(label="Objek Terdeteksi"), |
|
|
gr.Markdown(label="Informasi Tanaman (dari Gemini)"), |
|
|
gr.Markdown(label="Referensi Resep (dari File Lokal)") |
|
|
], |
|
|
title="🌿 Daunesia: Deteksi Cerdas Tanaman Herbal", |
|
|
description="Sistem deteksi dua-tahap. Unggah gambar untuk mengidentifikasi jenis tanaman, mendapatkan deskripsi dari AI, dan melihat resep dari file lokal.", |
|
|
examples=[ |
|
|
|
|
|
|
|
|
|
|
|
], |
|
|
allow_flagging="never", |
|
|
theme=gr.themes.Soft() |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
interface.launch() |