Spaces:
Sleeping
Sleeping
diahretnoutami
commited on
Commit
·
62d3e16
0
Parent(s):
Initial project commit for insect classifier
Browse files- .gitattributes +5 -0
- .gitignore +60 -0
- Dockerfile +33 -0
- ProyekCV_model.h5 +3 -0
- ProyekCV_model_v2.h5 +3 -0
- README.md +61 -0
- main.py +120 -0
- requirements.txt +7 -0
- static/frontend.html +177 -0
- static/gambar.png +3 -0
- static/home.html +54 -0
.gitattributes
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# File dan direktori yang dihasilkan oleh Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/ # Lingkungan virtual Python (penting untuk diabaikan!)
|
| 9 |
+
build/
|
| 10 |
+
develop-eggs/
|
| 11 |
+
dist/
|
| 12 |
+
downloads/
|
| 13 |
+
eggs/
|
| 14 |
+
.eggs/
|
| 15 |
+
lib/
|
| 16 |
+
lib64/
|
| 17 |
+
parts/
|
| 18 |
+
sdist/
|
| 19 |
+
var/
|
| 20 |
+
wheels/
|
| 21 |
+
share/python-wheels/
|
| 22 |
+
*.egg-info/
|
| 23 |
+
.installed.cfg
|
| 24 |
+
*.venv
|
| 25 |
+
pip-wheel-metadata/
|
| 26 |
+
.tox/
|
| 27 |
+
.nox/
|
| 28 |
+
.hypothesis/
|
| 29 |
+
.pytest_cache/
|
| 30 |
+
|
| 31 |
+
# File editor
|
| 32 |
+
.vscode/ # Konfigurasi VS Code
|
| 33 |
+
.idea/ # Konfigurasi IntelliJ/PyCharm
|
| 34 |
+
*.swp
|
| 35 |
+
*.bak
|
| 36 |
+
*~
|
| 37 |
+
|
| 38 |
+
# File khusus OS
|
| 39 |
+
.DS_Store # macOS
|
| 40 |
+
Thumbs.db # Windows
|
| 41 |
+
Desktop.ini # Windows
|
| 42 |
+
|
| 43 |
+
# Log dan file data sementara
|
| 44 |
+
*.log
|
| 45 |
+
*.tmp
|
| 46 |
+
*.temp
|
| 47 |
+
temp/
|
| 48 |
+
tmp/
|
| 49 |
+
.coverage
|
| 50 |
+
.coverage.*
|
| 51 |
+
|
| 52 |
+
# Data dan model yang mungkin besar dan tidak ingin di-commit jika dihasilkan
|
| 53 |
+
# oleh script atau jika terlalu besar untuk Git LFS (jika tidak pakai LFS)
|
| 54 |
+
# Meskipun model .h5 kamu di-commit, ini adalah contoh untuk kasus lain
|
| 55 |
+
# *.h5
|
| 56 |
+
# *.pb
|
| 57 |
+
# checkpoint/
|
| 58 |
+
|
| 59 |
+
# Jupyter Notebook files
|
| 60 |
+
.ipynb_checkpoints/ # Checkpoint notebook Jupyter
|
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gunakan image dasar Python yang ringan dan stabil
|
| 2 |
+
# Python 3.9 adalah versi yang umum dan cocok dengan banyak pustaka AI
|
| 3 |
+
FROM python:3.9-slim-buster
|
| 4 |
+
|
| 5 |
+
# Atur direktori kerja di dalam container.
|
| 6 |
+
# Semua operasi COPY dan RUN berikutnya akan dilakukan relatif terhadap direktori ini.
|
| 7 |
+
WORKDIR /app
|
| 8 |
+
|
| 9 |
+
# Salin file requirements.txt ke direktori kerja di container.
|
| 10 |
+
# Melakukan ini terlebih dahulu memungkinkan Docker untuk melakukan caching layer
|
| 11 |
+
# sehingga instalasi dependensi tidak perlu diulang jika hanya kode lain yang berubah.
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
|
| 14 |
+
# Instal semua dependensi Python yang terdaftar di requirements.txt.
|
| 15 |
+
# --no-cache-dir akan mencegah pip menyimpan cache unduhan, menghemat ruang.
|
| 16 |
+
# -r requirements.txt membaca daftar paket dari file.
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
# Salin semua file dan folder dari direktori lokal saat ini (root proyek kamu)
|
| 20 |
+
# ke direktori kerja di dalam container (/app).
|
| 21 |
+
# Ini termasuk main.py, model .h5, dan folder static/.
|
| 22 |
+
COPY . .
|
| 23 |
+
|
| 24 |
+
# Ekspos port yang akan digunakan aplikasi FastAPI kamu.
|
| 25 |
+
# Hugging Face Spaces secara internal akan mengarahkan traffic ke port 7860.
|
| 26 |
+
EXPOSE 7860
|
| 27 |
+
|
| 28 |
+
# Perintah untuk menjalankan aplikasi FastAPI kamu saat container dimulai.
|
| 29 |
+
# "uvicorn" adalah server ASGI.
|
| 30 |
+
# "main:app" berarti Uvicorn akan mencari objek bernama 'app' di file 'main.py'.
|
| 31 |
+
# "--host 0.0.0.0" membuat aplikasi dapat diakses dari luar container.
|
| 32 |
+
# "--port 7860" memastikan aplikasi mendengarkan pada port yang diharapkan oleh Hugging Face.
|
| 33 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
ProyekCV_model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:05c95dfa1b0b05112d641a428d1562394605d1f78516647e7d90febaf918fbe8
|
| 3 |
+
size 81826712
|
ProyekCV_model_v2.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:95b9408b466d3fa54a7cc3e6fdb0dd23c5bbca9b9050d55e52a3c33f53f966bd
|
| 3 |
+
size 11546936
|
README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🐞 Insect Classifier - FastAPI
|
| 2 |
+
|
| 3 |
+
A web-based insect classification app using two TensorFlow models (CNN and MobileNet), deployed with FastAPI. Users can upload insect images and receive predictions from both models, along with accuracy and descriptions.
|
| 4 |
+
|
| 5 |
+
## 🧠 Models
|
| 6 |
+
- CNN model (`cnn_model.h5`)
|
| 7 |
+
- MobileNet model (`mobilenet_model.h5`)
|
| 8 |
+
|
| 9 |
+
## ⚙️ Tech Stack
|
| 10 |
+
- FastAPI
|
| 11 |
+
- TensorFlow & Keras
|
| 12 |
+
- HTML/CSS (for frontend)
|
| 13 |
+
- Uvicorn (as ASGI server)
|
| 14 |
+
- Python
|
| 15 |
+
- NumPy & Pandas
|
| 16 |
+
- Matplotlib & Seaborn
|
| 17 |
+
- Scikit-learn
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
## 📦 Dataset
|
| 21 |
+
|
| 22 |
+
This project uses the [Insects Recognition Dataset](https://www.kaggle.com/datasets/hammaadali/insects-recognition) by Hammaad Ali, available on Kaggle.
|
| 23 |
+
|
| 24 |
+
**Dataset Features:**
|
| 25 |
+
- Contains high-quality images of 5 different insect classes. There are grasshopper, butterfly, mosquito, ladybird and dragonfly.
|
| 26 |
+
- Organized into labeled folders for each class.
|
| 27 |
+
- Ideal for supervised image classification tasks.
|
| 28 |
+
- Image format: `.jpg`
|
| 29 |
+
|
| 30 |
+
The dataset was used to train both the CNN and MobileNet models included in this project.
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
## 🚀 How to Run Locally
|
| 35 |
+
|
| 36 |
+
Make sure you have all dependencies installed and your virtual environment activated.
|
| 37 |
+
|
| 38 |
+
**Step 1: Start the FastAPI backend**
|
| 39 |
+
Open a terminal and run:
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
venv\Scripts\activate
|
| 43 |
+
uvicorn app.main:app --reload
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
The backend will be running at:
|
| 47 |
+
http://127.0.0.1:8000
|
| 48 |
+
|
| 49 |
+
**Step 2: Start a local HTTP server (for the frontend)**
|
| 50 |
+
in another terminal, terminal, activate your virtual environment and run:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
python -m http.server 8080
|
| 54 |
+
```
|
| 55 |
+
This will serve your frontend.html at:
|
| 56 |
+
http://127.0.0.1:8080/frontend.html
|
| 57 |
+
|
| 58 |
+
Now the frontend can communicate with the FastAPI backend.
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
|
main.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, File, UploadFile, Form
|
| 2 |
+
from fastapi.responses import JSONResponse, FileResponse
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
import os
|
| 5 |
+
from tensorflow.keras.models import load_model
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import numpy as np
|
| 8 |
+
from fastapi.staticfiles import StaticFiles
|
| 9 |
+
import io
|
| 10 |
+
import sys # <-- TAMBAH INI
|
| 11 |
+
|
| 12 |
+
# 1. Inisialisasi Aplikasi FastAPI di awal (PINDAHKAN KE SINI)
|
| 13 |
+
app = FastAPI()
|
| 14 |
+
|
| 15 |
+
# 2. Mounting Static Files
|
| 16 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 17 |
+
|
| 18 |
+
# 3. Konfigurasi CORS
|
| 19 |
+
origins = [
|
| 20 |
+
"http://localhost:8000", # <-- Sesuaikan dengan port lokal yang kamu gunakan
|
| 21 |
+
"http://127.0.0.1:8000", # <-- Sesuaikan dengan port lokal yang kamu gunakan
|
| 22 |
+
# Di Hugging Face, ini akan dihandle oleh domain space itu sendiri.
|
| 23 |
+
# Kamu bisa menambahkan domain HF Space kamu di sini jika ada masalah CORS setelah deploy.
|
| 24 |
+
# Contoh: "https://<nama-user-kamu>.hf.space/<nama-space-kamu>"
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
app.add_middleware(
|
| 28 |
+
CORSMiddleware,
|
| 29 |
+
allow_origins=origins,
|
| 30 |
+
allow_credentials=True,
|
| 31 |
+
allow_methods=["*"],
|
| 32 |
+
allow_headers=["*"],
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# 4. Memuat 2 Model
|
| 36 |
+
try:
|
| 37 |
+
cnn_model = load_model('ProyekCV_model.h5')
|
| 38 |
+
mobilenet_model = load_model('ProyekCV_model_v2.h5')
|
| 39 |
+
print("Kedua model (CNN dan MobileNet) berhasil dimuat!")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"Gagal memuat model: {e}")
|
| 42 |
+
sys.exit(1) # <-- TAMBAH INI UNTUK MENGHENTIKAN APLIKASI JIKA MODEL GAGAL DIMUAT
|
| 43 |
+
|
| 44 |
+
class_names = ['Butterfly', 'Dragonfly', 'Grasshopper', 'Ladybird', 'Mosquito']
|
| 45 |
+
|
| 46 |
+
descriptions = {
|
| 47 |
+
'Grasshopper': "Grasshopper adalah serangga herbivora yang dikenal dengan kemampuan melompat jauh berkat kaki belakangnya yang kuat. Mereka biasanya ditemukan di padang rumput atau lahan terbuka. Suara khas yang dihasilkan oleh grasshopper berasal dari gesekan sayap mereka. Hewan ini memegang peran penting dalam rantai makanan sebagai mangsa bagi berbagai predator. Dalam jumlah besar, beberapa spesies grasshopper dapat menjadi hama tanaman pertanian.",
|
| 48 |
+
'Butterfly': "Butterfly adalah serangga cantik dengan sayap berwarna-warni yang hidup di berbagai habitat. Mereka mengalami metamorfosis lengkap dari larva menjadi dewasa. Butterfly sering dikaitkan dengan penyerbukan tanaman. Kehadiran mereka menandakan ekosistem yang sehat. Beberapa spesies butterfly terancam punah karena kerusakan habitat.",
|
| 49 |
+
'Dragonfly': "Dragonfly adalah serangga pemangsa yang hidup di dekat air. Mereka terbang sangat cepat dan lincah, memakan serangga lain seperti nyamuk. Dragonfly memiliki mata besar yang memberikan penglihatan hampir 360 derajat. Mereka berperan penting dalam mengontrol populasi serangga hama. Larvanya hidup di air sebelum bermetamorfosis menjadi dewasa.",
|
| 50 |
+
'Ladybird': "Ladybird, atau kepik, adalah serangga kecil berwarna cerah dengan bintik-bintik di punggungnya. Mereka dikenal sebagai predator alami kutu daun. Ladybird dianggap menguntungkan bagi petani karena membantu mengendalikan hama. Terdapat berbagai spesies ladybird dengan pola warna yang berbeda. Beberapa budaya menganggap ladybird sebagai pembawa keberuntungan.",
|
| 51 |
+
'Mosquito': "Mosquito adalah serangga kecil yang dikenal sebagai penghisap darah. Beberapa spesies dapat menularkan penyakit seperti malaria dan dengue. Hanya nyamuk betina yang menggigit manusia untuk mendapatkan protein dari darah. Mereka berkembang biak di air tergenang. Pengendalian populasi nyamuk penting untuk kesehatan masyarakat."
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
def preprocess_image(image):
|
| 55 |
+
img = image.resize((150, 150))
|
| 56 |
+
img = np.array(img) / 255.0
|
| 57 |
+
img = np.expand_dims(img, axis=0)
|
| 58 |
+
return img
|
| 59 |
+
|
| 60 |
+
@app.post("/predict/")
|
| 61 |
+
async def predict(file: UploadFile = File(...)):
|
| 62 |
+
image = Image.open(io.BytesIO(await file.read())).convert("RGB")
|
| 63 |
+
img = preprocess_image(image)
|
| 64 |
+
|
| 65 |
+
# CNN
|
| 66 |
+
cnn_pred = cnn_model.predict(img)
|
| 67 |
+
cnn_class = int(np.argmax(cnn_pred[0]))
|
| 68 |
+
cnn_conf = float(np.max(cnn_pred[0]))
|
| 69 |
+
|
| 70 |
+
if cnn_conf < 0.5:
|
| 71 |
+
cnn_label = "Tidak Dikenali"
|
| 72 |
+
cnn_desc = "Gambar tidak dapat dikenali dengan tingkat kepercayaan yang memadai oleh model CNN."
|
| 73 |
+
else:
|
| 74 |
+
cnn_label = class_names[cnn_class]
|
| 75 |
+
cnn_desc = descriptions.get(cnn_label, "Deskripsi tidak tersedia.")
|
| 76 |
+
|
| 77 |
+
# MobileNet
|
| 78 |
+
mobile_pred = mobilenet_model.predict(img)
|
| 79 |
+
mobile_class = int(np.argmax(mobile_pred[0]))
|
| 80 |
+
mobile_conf = float(np.max(mobile_pred[0]))
|
| 81 |
+
|
| 82 |
+
if mobile_conf < 0.5:
|
| 83 |
+
mobile_label = "Tidak Dikenali"
|
| 84 |
+
mobile_desc = "Gambar tidak dapat dikenali dengan tingkat kepercayaan yang memadai oleh model MobileNet."
|
| 85 |
+
else:
|
| 86 |
+
mobile_label = class_names[mobile_class]
|
| 87 |
+
mobile_desc = descriptions.get(mobile_label, "Deskripsi tidak tersedia.")
|
| 88 |
+
|
| 89 |
+
final_desc = cnn_desc # Default to CNN desc
|
| 90 |
+
if cnn_label == "Tidak Dikenali": # If CNN doesn't recognize
|
| 91 |
+
final_desc = mobile_desc # Try MobileNet's desc
|
| 92 |
+
if mobile_label == "Tidak Dikenali": # If both don't recognize
|
| 93 |
+
final_desc = "Gambar tidak dapat dikenali oleh kedua model."
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
return JSONResponse(content={
|
| 97 |
+
"cnn": {
|
| 98 |
+
"predicted_class": cnn_label,
|
| 99 |
+
"confidence": cnn_conf,
|
| 100 |
+
"description": cnn_desc
|
| 101 |
+
},
|
| 102 |
+
"mobilenet": {
|
| 103 |
+
"predicted_class": mobile_label,
|
| 104 |
+
"confidence": mobile_conf,
|
| 105 |
+
"description": mobile_desc
|
| 106 |
+
},
|
| 107 |
+
"overall_description": final_desc
|
| 108 |
+
})
|
| 109 |
+
|
| 110 |
+
@app.get("/")
|
| 111 |
+
async def get_home():
|
| 112 |
+
return FileResponse("static/home.html")
|
| 113 |
+
|
| 114 |
+
@app.get("/frontend.html")
|
| 115 |
+
async def get_frontend():
|
| 116 |
+
return FileResponse("static/frontend.html")
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
import uvicorn
|
| 120 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
tensorflow==2.18.0 # Gunakan versi spesifik yang kamu gunakan saat training, misal: tensorflow==2.10.0
|
| 4 |
+
Pillow
|
| 5 |
+
python-multipart
|
| 6 |
+
numpy
|
| 7 |
+
scipy # Penting untuk tensorflow
|
static/frontend.html
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Insect Classifier</title>
|
| 6 |
+
<style>
|
| 7 |
+
/* Hapus kata 'padding' yang salah posisi */
|
| 8 |
+
body {
|
| 9 |
+
font-family: Arial, sans-serif;
|
| 10 |
+
background: #f0f8ff;
|
| 11 |
+
text-align: center;
|
| 12 |
+
margin: 0;
|
| 13 |
+
padding: 20px;
|
| 14 |
+
}
|
| 15 |
+
h1 {
|
| 16 |
+
color: #333;
|
| 17 |
+
}
|
| 18 |
+
h4 {
|
| 19 |
+
color: #666;
|
| 20 |
+
margin-top: 0;
|
| 21 |
+
}
|
| 22 |
+
.container {
|
| 23 |
+
display: flex;
|
| 24 |
+
justify-content: center;
|
| 25 |
+
gap: 30px;
|
| 26 |
+
margin-top: 30px;
|
| 27 |
+
}
|
| 28 |
+
.card {
|
| 29 |
+
background: white;
|
| 30 |
+
padding: 20px;
|
| 31 |
+
border-radius: 12px;
|
| 32 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
| 33 |
+
width: 300px;
|
| 34 |
+
height: 450px; /* Diperhatikan apakah cukup tinggi jika deskripsi panjang */
|
| 35 |
+
display: flex;
|
| 36 |
+
flex-direction: column;
|
| 37 |
+
align-items: center;
|
| 38 |
+
}
|
| 39 |
+
input[type="file"] {
|
| 40 |
+
margin: 10px 0;
|
| 41 |
+
}
|
| 42 |
+
/* #modelSelect tidak digunakan di HTML ini, bisa dihapus jika tidak ada elemennya */
|
| 43 |
+
/*
|
| 44 |
+
#modelSelect {
|
| 45 |
+
margin: 10px 0;
|
| 46 |
+
padding: 5px;
|
| 47 |
+
font-size: 14px;
|
| 48 |
+
}
|
| 49 |
+
*/
|
| 50 |
+
#preview {
|
| 51 |
+
max-width: 90%;
|
| 52 |
+
max-height: 150px;
|
| 53 |
+
border-radius: 10px;
|
| 54 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
| 55 |
+
margin-bottom: 10px;
|
| 56 |
+
}
|
| 57 |
+
button {
|
| 58 |
+
background-color: #4CAF50;
|
| 59 |
+
color: white;
|
| 60 |
+
padding: 10px 20px;
|
| 61 |
+
border: none;
|
| 62 |
+
border-radius: 8px;
|
| 63 |
+
cursor: pointer;
|
| 64 |
+
font-size: 16px;
|
| 65 |
+
}
|
| 66 |
+
button:hover {
|
| 67 |
+
background-color: #45a049;
|
| 68 |
+
}
|
| 69 |
+
.loading {
|
| 70 |
+
display: none;
|
| 71 |
+
margin-top: 10px;
|
| 72 |
+
font-style: italic;
|
| 73 |
+
color: #555;
|
| 74 |
+
}
|
| 75 |
+
#result {
|
| 76 |
+
font-size: 18px;
|
| 77 |
+
font-weight: bold;
|
| 78 |
+
color: #333;
|
| 79 |
+
margin-top: 20px;
|
| 80 |
+
white-space: pre-line;
|
| 81 |
+
}
|
| 82 |
+
/* Menggabungkan style description dan pseudo-elementnya */
|
| 83 |
+
#description {
|
| 84 |
+
margin-top: 20px;
|
| 85 |
+
text-align: justify;
|
| 86 |
+
font-size: 14px;
|
| 87 |
+
color: #555;
|
| 88 |
+
border-top: 1px solid #ccc; /* Garis pemisah */
|
| 89 |
+
padding-top: 15px; /* Spasi setelah garis */
|
| 90 |
+
}
|
| 91 |
+
</style>
|
| 92 |
+
</head>
|
| 93 |
+
<body>
|
| 94 |
+
|
| 95 |
+
<h1>Insect Classification</h1>
|
| 96 |
+
<h4>Diah Retno Utami - 4TIB</h4>
|
| 97 |
+
|
| 98 |
+
<div class="container">
|
| 99 |
+
<div class="card">
|
| 100 |
+
<h2>Upload Image</h2>
|
| 101 |
+
<input type="file" id="upload" accept="image/*" onchange="previewImage()"/>
|
| 102 |
+
<img id="preview" src="" alt="Image Preview"/>
|
| 103 |
+
<button onclick="uploadImage()">Predict</button>
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<div class="card">
|
| 107 |
+
<h2>Prediction Result</h2>
|
| 108 |
+
<div id="result">No prediction yet.</div>
|
| 109 |
+
<p class="loading" id="loading">Predicting...</p>
|
| 110 |
+
<div id="description"></div>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
<script>
|
| 116 |
+
function previewImage() {
|
| 117 |
+
const file = document.getElementById('upload').files[0];
|
| 118 |
+
if (file) { // Tambahkan pengecekan file ada atau tidak
|
| 119 |
+
const reader = new FileReader();
|
| 120 |
+
reader.onload = function(e) {
|
| 121 |
+
document.getElementById('preview').src = e.target.result;
|
| 122 |
+
}
|
| 123 |
+
reader.readAsDataURL(file);
|
| 124 |
+
} else {
|
| 125 |
+
document.getElementById('preview').src = ""; // Reset gambar jika tidak ada file
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async function uploadImage() {
|
| 130 |
+
const fileInput = document.getElementById('upload');
|
| 131 |
+
const file = fileInput.files[0];
|
| 132 |
+
|
| 133 |
+
if (!file) {
|
| 134 |
+
alert('Please select an image.');
|
| 135 |
+
return;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
const formData = new FormData();
|
| 139 |
+
formData.append("file", file);
|
| 140 |
+
|
| 141 |
+
document.getElementById('loading').style.display = 'block';
|
| 142 |
+
document.getElementById('result').innerText = 'No prediction yet.';
|
| 143 |
+
document.getElementById('description').innerText = ''; // Reset deskripsi
|
| 144 |
+
|
| 145 |
+
try {
|
| 146 |
+
// !!! KRITIKAL: UBAH URL API INI UNTUK KOMPATIBILITAS DEPLOYMENT DAN LOKAL !!!
|
| 147 |
+
const response = await fetch('/predict/', { // <-- PERUBAHAN DI SINI
|
| 148 |
+
method: 'POST',
|
| 149 |
+
body: formData
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
if (!response.ok) { // Tambahkan pengecekan jika response tidak OK (misal 404, 500)
|
| 153 |
+
const errorText = await response.text();
|
| 154 |
+
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
const result = await response.json();
|
| 158 |
+
|
| 159 |
+
document.getElementById('result').innerText =
|
| 160 |
+
`CNN Model:\n Predicted Class: ${result.cnn.predicted_class}\n Accuracy: ${(result.cnn.confidence * 100).toFixed(2)}%\n\n` +
|
| 161 |
+
`MobileNetV2 Model:\n Predicted Class: ${result.mobilenet.predicted_class}\n Accuracy: ${(result.mobilenet.confidence * 100).toFixed(2)}%`;
|
| 162 |
+
|
| 163 |
+
// !!! KRITIKAL: GUNAKAN overall_description DARI BACKEND !!!
|
| 164 |
+
document.getElementById('description').innerText = result.overall_description; // <-- PERUBAHAN DI SINI
|
| 165 |
+
|
| 166 |
+
} catch (error) {
|
| 167 |
+
console.error("Error during prediction:", error); // Log error lebih detail untuk debugging
|
| 168 |
+
document.getElementById('result').innerText = `Error during prediction: ${error.message || error}`;
|
| 169 |
+
document.getElementById('description').innerText = 'Failed to get description.';
|
| 170 |
+
} finally {
|
| 171 |
+
document.getElementById('loading').style.display = 'none';
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
</script>
|
| 175 |
+
|
| 176 |
+
</body>
|
| 177 |
+
</html>
|
static/gambar.png
ADDED
|
Git LFS Details
|
static/home.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- app/home.html -->
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html lang="en">
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<title>Selamat Datang</title>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
display: flex;
|
| 10 |
+
justify-content: center;
|
| 11 |
+
align-items: center;
|
| 12 |
+
height: 100vh;
|
| 13 |
+
margin: 0;
|
| 14 |
+
font-family: Arial, sans-serif;
|
| 15 |
+
background-color: #f4f4f4;
|
| 16 |
+
}
|
| 17 |
+
.card {
|
| 18 |
+
background: white;
|
| 19 |
+
padding: 2rem;
|
| 20 |
+
border-radius: 10px;
|
| 21 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| 22 |
+
text-align: center;
|
| 23 |
+
max-width: 500px;
|
| 24 |
+
}
|
| 25 |
+
.card h1 {
|
| 26 |
+
margin-bottom: 1rem;
|
| 27 |
+
}
|
| 28 |
+
.card p {
|
| 29 |
+
margin-bottom: 2rem;
|
| 30 |
+
}
|
| 31 |
+
.btn {
|
| 32 |
+
padding: 0.75rem 1.5rem;
|
| 33 |
+
background-color: #4CAF50;
|
| 34 |
+
color: white;
|
| 35 |
+
border: none;
|
| 36 |
+
border-radius: 5px;
|
| 37 |
+
text-decoration: none;
|
| 38 |
+
font-size: 1rem;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
}
|
| 41 |
+
.btn:hover {
|
| 42 |
+
background-color: #45a049;
|
| 43 |
+
}
|
| 44 |
+
</style>
|
| 45 |
+
</head>
|
| 46 |
+
<body>
|
| 47 |
+
<div class="card">
|
| 48 |
+
<img src="/static/gambar.png" alt="Logo Serangga" style="width: 100%; max-width: 200px; height: auto; margin-bottom: 1rem;">
|
| 49 |
+
<h1>Selamat Datang di Sistem Klasifikasi Serangga</h1>
|
| 50 |
+
<p>Website ini menggunakan dua model deep learning (CNN dan MobileNet) untuk mengklasifikasikan gambar serangga secara otomatis.</p>
|
| 51 |
+
<a href="/frontend.html" class="btn">Mulai</a>
|
| 52 |
+
</div>
|
| 53 |
+
</body>
|
| 54 |
+
</html>
|