diahretnoutami commited on
Commit
62d3e16
·
0 Parent(s):

Initial project commit for insect classifier

Browse files
.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

  • SHA256: 5d0ffb4b1c9c17ed3909b5c357f3bdc8dad73a030adb2a931e69d3712fae8122
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
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>