Upload 4 files
Browse files- Dockerfile +21 -0
- app.py +93 -0
- model.h5 +3 -0
- requirements.txt +13 -0
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gunakan image Python resmi yang ringan
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set lingkungan kerja di dalam container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Salin requirements dan instal library
|
| 8 |
+
COPY requirements.txt .
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# Salin seluruh isi folder proyek ke dalam container
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Berikan izin akses folder (untuk Hugging Face)
|
| 15 |
+
RUN chmod -R 777 /app
|
| 16 |
+
|
| 17 |
+
# Port yang digunakan Gradio
|
| 18 |
+
EXPOSE 7860
|
| 19 |
+
|
| 20 |
+
# Jalankan aplikasi
|
| 21 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import tensorflow as tf
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from PIL import Image
|
| 7 |
+
|
| 8 |
+
# --- KONFIGURASI GLOBAL ---
|
| 9 |
+
IMG_SIZE = (224, 224)
|
| 10 |
+
MODEL_PATH = 'model.h5'
|
| 11 |
+
class_names = ['Aluvial', 'Andosol', 'Entisol', 'Humus', 'Inceptisol', 'Laterit', 'Kapur', 'Pasir']
|
| 12 |
+
num_classes = len(class_names)
|
| 13 |
+
history_data = []
|
| 14 |
+
|
| 15 |
+
# --- LOAD MODEL ---
|
| 16 |
+
try:
|
| 17 |
+
best_model = tf.keras.models.load_model(MODEL_PATH)
|
| 18 |
+
print("β
Model loaded successfully")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
best_model = None
|
| 21 |
+
print(f"β Error loading model: {e}")
|
| 22 |
+
|
| 23 |
+
incident_descriptions = {
|
| 24 |
+
'Aluvial': ("πΎ **Aluvial (Tanah Endapan Sungai)**\n\n**Karakteristik:** Sangat subur karena membawa mineral dari hulu sungai.\n**Rekomendasi Tanaman:** Padi, jagung, kedelai, atau tebu.\n**Langkah Pemulihan:**\n1. **Bersihkan Lumpur Pekat:** Buang lapisan lumpur keras agar akar bisa bernapas.\n2. **Cek Saluran Air:** Pastikan air tidak menggenang (becek).\n3. **Gemburkan Ulang:** Cangkul ringan agar oksigen masuk ke tanah."),
|
| 25 |
+
'Andosol': ("π **Andosol (Tanah Vulkanik)**\n\n**Karakteristik:** Tanah hitam sangat kaya nutrisi abu gunung berapi.\n**Rekomendasi Tanaman:** Sayuran, kopi, atau teh.\n**Langkah Pemulihan:**\n1. **Terasering:** Buat gundukan bertingkat pada lahan miring.\n2. **Lindungi dari Angin:** Tanam pohon pelindung di pinggir lahan.\n3. **Siram Teratur:** Tanah ini cepat kering jika terpapar matahari langsung."),
|
| 26 |
+
'Entisol': ("π± **Entisol (Tanah Muda)**\n\n**Karakteristik:** Tanah baru terbentuk, agak kasar dan belum matang.\n**Rekomendasi Tanaman:** Rumput pakan, ubi kayu, atau kacang tanah.\n**Langkah Pemulihan:**\n1. **Beri 'Makan' Tanah:** Campur pupuk kandang/kompos sebanyak mungkin.\n2. **Tanam Kacang-kacangan:** Membantu tanah mengambil nitrogen alami dari udara.\n3. **Hindari Bahan Kimia:** Gunakan pupuk alami agar struktur tanah tidak rusak."),
|
| 27 |
+
'Humus': ("π **Humus (Tanah Organik)**\n\n**Karakteristik:** Tanah paling subur dari pembusukan vegetasi alami.\n**Rekomendasi Tanaman:** Semua jenis tanaman pangan dan sayuran.\n**Langkah Pemulihan:**\n1. **Selimut Tanah (Mulsa):** Tutupi tanah dengan jerami/daun kering.\n2. **Jangan Dibakar:** Hindari membakar sampah di atas tanah ini.\n3. **Jaga Kelembapan:** Cukup siram secukupnya, jangan berlebihan."),
|
| 28 |
+
'Inceptisol': ("π³ **Inceptisol (Tanah Perkebunan)**\n\n**Karakteristik:** Tanah perkebunan yang nutrisinya mulai berkurang.\n**Rekomendasi Tanaman:** Kelapa sawit, karet, atau buah-buahan.\n**Langkah Pemulihan:**\n1. **Pupuk Berkala:** Gunakan pupuk NPK sesuai dosis rutin.\n2. **Bersihkan Gulma:** Cabut rumput liar di sekitar batang pohon.\n3. **Cangkul Melingkar:** Cangkul di sekitar pohon agar pupuk meresap ke akar."),
|
| 29 |
+
'Laterit': ("π΄ **Laterit (Tanah Merah/Asam)**\n\n**Karakteristik:** Tanah tua kemerahan dan terasa masam.\n**Rekomendasi Tanaman:** Cengkeh, jambu mete, atau karet.\n**Langkah Pemulihan:**\n1. **Tabur Kapur (Dolomit):** Gunakan kapur putih untuk menetralkan asam.\n2. **Tambah Pupuk Organik:** Campurkan pupuk kandang untuk memperbaiki warna.\n3. **Hindari Genangan:** Jangan biarkan air mengendap agar tanah tidak memadat."),
|
| 30 |
+
'Kapur': ("βͺ **Kapur (Tanah Mediteran)**\n\n**Karakteristik:** Berbatu putih, gersang, dan cepat kering.\n**Rekomendasi Tanaman:** Pohon jati, mahoni, atau srikaya.\n**Langkah Pemulihan:**\n1. **Tanam Pohon Keras:** Pilih pohon dengan akar kuat penembus batu.\n2. **Pupuk Hijau:** Tanam tumbuhan merambat sebagai penutup tanah.\n3. **Lubang Tanam Besar:** Isi lubang tanam dengan banyak kompos."),
|
| 31 |
+
'Pasir': ("ποΈ **Pasir (Tanah Berpori)**\n\n**Karakteristik:** Butiran kasar, tidak simpan air, cepat panas.\n**Rekomendasi Tanaman:** Kelapa, buah naga, atau ubi jalar.\n**Langkah Pemulihan:**\n1. **Ikat Tanah:** Campur pupuk kandang dalam jumlah besar.\n2. **Siram Sore Hari:** Siram saat matahari tidak terik agar air tidak menguap.\n3. **Pupuk Sedikit tapi Sering:** Beri dosis kecil secara rutin.")
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
def predict_image(image_input):
|
| 35 |
+
if best_model is None:
|
| 36 |
+
return {}, "Model tidak tersedia."
|
| 37 |
+
|
| 38 |
+
img = image_input.resize(IMG_SIZE)
|
| 39 |
+
img_array = tf.keras.utils.img_to_array(img)
|
| 40 |
+
img_array = tf.expand_dims(img_array, 0) / 255.0
|
| 41 |
+
|
| 42 |
+
predictions = best_model.predict(img_array)
|
| 43 |
+
scores = tf.nn.softmax(predictions[0]).numpy()
|
| 44 |
+
|
| 45 |
+
output_dict = {class_names[i]: float(scores[i]) for i in range(num_classes)}
|
| 46 |
+
top_label = max(output_dict, key=output_dict.get)
|
| 47 |
+
top_confidence = output_dict[top_label]
|
| 48 |
+
|
| 49 |
+
description = incident_descriptions.get(top_label, "Deskripsi tidak tersedia.")
|
| 50 |
+
|
| 51 |
+
return output_dict, f"### π Prediksi: **{top_label}** ({top_confidence:.2%})\n\n{description}"
|
| 52 |
+
|
| 53 |
+
def predict_only(img):
|
| 54 |
+
if img is None: return {}, "Silakan upload gambar."
|
| 55 |
+
return predict_image(img)
|
| 56 |
+
|
| 57 |
+
def predict_with_metadata(img, location):
|
| 58 |
+
if img is None:
|
| 59 |
+
return {}, pd.DataFrame(history_data, columns=["Waktu", "Jenis Tanah", "Lokasi"]), None, "Tidak ada gambar."
|
| 60 |
+
|
| 61 |
+
output_dict, formatted_desc = predict_image(img)
|
| 62 |
+
top_label = max(output_dict, key=output_dict.get)
|
| 63 |
+
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
| 64 |
+
|
| 65 |
+
history_data.append([now, top_label, location if location else "-"])
|
| 66 |
+
history_df = pd.DataFrame(history_data, columns=["Waktu", "Jenis Tanah", "Lokasi"])
|
| 67 |
+
|
| 68 |
+
return output_dict, history_df, None, formatted_desc
|
| 69 |
+
|
| 70 |
+
# --- UI GRADIO ---
|
| 71 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald")) as demo:
|
| 72 |
+
gr.Markdown("# π GeoHeal AI Dashboard")
|
| 73 |
+
gr.Markdown("Deteksi jenis tanah untuk pemulihan lahan pertanian pasca bencana.")
|
| 74 |
+
|
| 75 |
+
with gr.Tabs():
|
| 76 |
+
with gr.TabItem("πΈ Analisis Real-time"):
|
| 77 |
+
with gr.Row():
|
| 78 |
+
with gr.Column():
|
| 79 |
+
input_img = gr.Image(sources=["upload", "webcam"], type="pil", label="Foto Tanah")
|
| 80 |
+
input_loc = gr.Textbox(label="Lokasi (Opsional)", placeholder="Contoh: Medan, Sumut")
|
| 81 |
+
btn_report = gr.Button("π LAPORKAN KONDISI", variant="primary")
|
| 82 |
+
with gr.Column():
|
| 83 |
+
output_label = gr.Label(num_top_classes=3, label="Hasil Prediksi")
|
| 84 |
+
output_desc = gr.Markdown("_Hasil akan muncul di sini_")
|
| 85 |
+
|
| 86 |
+
with gr.TabItem("π Riwayat Laporan"):
|
| 87 |
+
output_history = gr.Dataframe(headers=["Waktu", "Jenis Tanah", "Lokasi"], interactive=False)
|
| 88 |
+
|
| 89 |
+
input_img.change(fn=predict_only, inputs=input_img, outputs=[output_label, output_desc])
|
| 90 |
+
btn_report.click(fn=predict_with_metadata, inputs=[input_img, input_loc], outputs=[output_label, output_history, input_img, output_desc])
|
| 91 |
+
|
| 92 |
+
if __name__ == "__main__":
|
| 93 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8e510a1f33ddeb02a1087e00f5420327c5c2ff4394368ba41252e62d46486a93
|
| 3 |
+
size 41589144
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tensorflow==2.19.0
|
| 2 |
+
keras==3.10.0
|
| 3 |
+
numpy==2.0.2
|
| 4 |
+
matplotlib==3.10.0
|
| 5 |
+
seaborn==0.13.2
|
| 6 |
+
google-colab==1.0.0
|
| 7 |
+
tensorflow-hub==0.16.1
|
| 8 |
+
kagglehub==0.3.13
|
| 9 |
+
scikit-learn==1.6.1
|
| 10 |
+
tensorflowjs==4.22.0
|
| 11 |
+
gradio==5.50.0
|
| 12 |
+
Pillow==11.3.0
|
| 13 |
+
pandas==2.2.2
|