Dyna-99 commited on
Commit
6029ea5
·
verified ·
1 Parent(s): ac2ce6d

Upload Semua File untuk api

Browse files
Files changed (4) hide show
  1. Dockerfile +26 -0
  2. app.py +501 -0
  3. model.h5 +3 -0
  4. requirements.txt +6 -0
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gunakan image Python
2
+ FROM python:3.10-slim
3
+
4
+ # Install dependencies sistem
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ libglib2.0-0 \
8
+ libsm6 \
9
+ libxext6 \
10
+ libxrender-dev \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Buat working directory
14
+ WORKDIR /app
15
+
16
+ # Salin file
17
+ COPY . .
18
+
19
+ # Install requirements
20
+ RUN pip install --no-cache-dir -r requirements.txt
21
+
22
+ # Expose port
23
+ EXPOSE 7860
24
+
25
+ # Jalankan app
26
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import numpy as np
4
+ from tensorflow.keras.preprocessing.image import load_img, img_to_array
5
+ import tensorflow as tf
6
+ import os
7
+ from PIL import Image
8
+ import io
9
+ import base64
10
+ import cv2
11
+
12
+ app = Flask(__name__)
13
+
14
+ # CORS Configuration - Lebih spesifik
15
+ CORS(app, resources={
16
+ r"/*": {
17
+ "origins": ["http://localhost", "http://127.0.0.1", "http://localhost:8000", "http://127.0.0.1:8000"],
18
+ "methods": ["GET", "POST", "OPTIONS"],
19
+ "allow_headers": ["Content-Type", "Authorization"]
20
+ }
21
+ })
22
+
23
+ # Configuration
24
+ UPLOAD_FOLDER = 'uploads'
25
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
26
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024
27
+
28
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
29
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
30
+
31
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
32
+
33
+ # Load model
34
+ try:
35
+ model = tf.keras.models.load_model('model.h5')
36
+ print("Model loaded successfully!")
37
+
38
+ # Print model details for debugging
39
+ print(f" Model input shape: {model.input_shape}")
40
+ print(f" Model output shape: {model.output_shape}")
41
+ print(f" Number of classes: {model.output_shape[-1]}")
42
+
43
+ except Exception as e:
44
+ print(f"Error loading model: {e}")
45
+ model = None
46
+
47
+ # Class names - pastikan urutan sama dengan training
48
+ class_names =[
49
+ 'Bercak_bakteri',
50
+ 'Bercak_daun_Septoria',
51
+ 'Bercak_Target',
52
+ 'Bercak_daun_awal',
53
+ 'Busuk_daun_lanjut',
54
+ 'Embun_tepung',
55
+ 'Jamur_daun',
56
+ 'Sehat',
57
+ 'Tungau_dua_bercak',
58
+ 'Virus_keriting_daun_kuning',
59
+ 'Virus_mosaik_tomat',
60
+ ]
61
+
62
+ def validate_tomato_leaf_image(image):
63
+ """
64
+ Validasi apakah gambar adalah daun tomat menggunakan beberapa metode
65
+ Returns: (is_valid, reason, confidence)
66
+ """
67
+ try:
68
+ # Convert PIL to OpenCV format
69
+ img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
70
+
71
+ # 1. Color Analysis - Cek dominasi warna hijau
72
+ hsv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2HSV)
73
+
74
+ # Define green color range in HSV
75
+ lower_green1 = np.array([35, 40, 40]) # Light green
76
+ upper_green1 = np.array([85, 255, 255]) # Dark green
77
+
78
+ # Create mask for green colors
79
+ green_mask = cv2.inRange(hsv, lower_green1, upper_green1)
80
+ green_ratio = np.sum(green_mask > 0) / (green_mask.shape[0] * green_mask.shape[1])
81
+
82
+ print(f"Green color ratio: {green_ratio:.3f}")
83
+
84
+ # 2. Edge Detection - Cek apakah ada struktur daun
85
+ gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
86
+ edges = cv2.Canny(gray, 50, 150)
87
+ edge_ratio = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
88
+
89
+ print(f"Edge ratio: {edge_ratio:.3f}")
90
+
91
+ # 3. Aspect Ratio - Daun biasanya tidak terlalu ekstrem
92
+ height, width = image.size[1], image.size[0]
93
+ aspect_ratio = max(width, height) / min(width, height)
94
+
95
+ print(f"Aspect ratio: {aspect_ratio:.2f}")
96
+
97
+ # 4. Brightness and Contrast Analysis
98
+ gray_array = np.array(gray)
99
+ brightness = np.mean(gray_array)
100
+ contrast = np.std(gray_array)
101
+
102
+ print(f"Brightness: {brightness:.2f}, Contrast: {contrast:.2f}")
103
+
104
+ # Validation Rules - Lebih permisif
105
+ reasons = []
106
+
107
+ # Rule 1: Must have sufficient green color (at least 10% - lebih permisif)
108
+ if green_ratio < 0.10:
109
+ reasons.append(f"Kurang dominasi warna hijau ({green_ratio*100:.1f}%)")
110
+
111
+ # Rule 2: Must have reasonable edge structure (0.01-0.4 - lebih permisif)
112
+ if edge_ratio < 0.01:
113
+ reasons.append("Struktur gambar terlalu sederhana")
114
+ elif edge_ratio > 0.4:
115
+ reasons.append("Struktur gambar terlalu kompleks")
116
+
117
+ # Rule 3: Aspect ratio shouldn't be too extreme (lebih permisif)
118
+ if aspect_ratio > 10:
119
+ reasons.append(f"Rasio aspek terlalu ekstrem ({aspect_ratio:.1f}:1)")
120
+
121
+ # Rule 4: Brightness should be reasonable (lebih permisif)
122
+ if brightness < 20:
123
+ reasons.append("Gambar terlalu gelap")
124
+ elif brightness > 220:
125
+ reasons.append("Gambar terlalu terang")
126
+
127
+ # Rule 5: Should have reasonable contrast (lebih permisif)
128
+ if contrast < 15:
129
+ reasons.append("Kontras gambar terlalu rendah")
130
+
131
+ # Calculate confidence based on how well it matches leaf characteristics
132
+ confidence = 0
133
+ confidence += min(green_ratio * 2.5, 0.4) # Max 40% for green ratio
134
+ confidence += min(edge_ratio * 4, 0.3) # Max 30% for edge structure
135
+ confidence += max(0, 0.2 - (aspect_ratio - 1) * 0.02) # Max 20% for aspect ratio
136
+ confidence += min((brightness - 30) / 120 * 0.1, 0.1) # Max 10% for brightness
137
+
138
+ # Lebih permisif untuk confidence threshold
139
+ is_valid = len(reasons) == 0 and confidence > 0.2
140
+
141
+ return is_valid, reasons, confidence
142
+
143
+ except Exception as e:
144
+ print(f"Validation error: {e}")
145
+ return True, [], 0.5 # Lebih permisif jika ada error validasi
146
+
147
+ def validate_with_model_confidence(prediction, confidence_threshold=0.4): # Threshold lebih rendah
148
+ """
149
+ Validasi tambahan berdasarkan confidence model
150
+ Jika confidence terlalu rendah, kemungkinan bukan daun tomat
151
+ """
152
+ max_confidence = np.max(prediction)
153
+
154
+ if max_confidence < confidence_threshold:
155
+ # Cek apakah prediksi terdistribusi merata (sign of uncertainty)
156
+ sorted_probs = np.sort(prediction[0])[::-1]
157
+ top_diff = sorted_probs[0] - sorted_probs[1]
158
+
159
+ if top_diff < 0.15: # Lebih permisif
160
+ return False, f"Model tidak yakin dengan prediksi (confidence: {max_confidence*100:.1f}%)"
161
+
162
+ return True, None
163
+
164
+ def preprocess_image(image, target_size=(224, 224)):
165
+ from tensorflow.keras.applications.resnet50 import preprocess_input
166
+
167
+ if image.mode != 'RGB':
168
+ image = image.convert('RGB')
169
+
170
+ image = image.resize(target_size)
171
+ img_array = img_to_array(image)
172
+ img_array = np.expand_dims(img_array, axis=0)
173
+
174
+ # Use the same preprocessing as during training
175
+ img_array = preprocess_input(img_array)
176
+
177
+ return img_array
178
+
179
+ def allowed_file(filename):
180
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
181
+
182
+ def is_healthy_plant(class_name):
183
+ """Determine if the predicted class represents a healthy plant"""
184
+ healthy_classes = ['Sehat', 'healthy', 'Tanaman_Sehat']
185
+ return class_name in healthy_classes
186
+
187
+ def get_disease_info(disease_name):
188
+ """Get disease information"""
189
+ info = {
190
+ 'Bercak_bakteri': {
191
+ 'name': 'Bercak Bakteri',
192
+ 'symptoms': 'Bercak coklat kecil dengan tepi kuning pada daun, buah, dan batang',
193
+ 'causes': 'Bakteri Xanthomonas campestris',
194
+ 'prevention': 'Gunakan benih bebas penyakit, hindari penyiraman dari atas, rotasi tanaman',
195
+ 'treatment': 'Gunakan bakterisida yang mengandung tembaga, praktikkan rotasi tanaman',
196
+ 'severity': 'sedang'
197
+ },
198
+ 'Bercak_daun_Septoria': {
199
+ 'name': 'Bercak Daun Septoria',
200
+ 'symptoms': 'Bercak bulat kecil dengan pusat abu-abu dan tepi coklat pada daun',
201
+ 'causes': 'Jamur Septoria lycopersici',
202
+ 'prevention': 'Hindari penyiraman dari atas, mulsa tanah, rotasi tanaman',
203
+ 'treatment': 'Hapus daun yang terinfeksi dan gunakan fungisida yang mengandung tembaga',
204
+ 'severity': 'sedang'
205
+ },
206
+ 'Bercak_Target': {
207
+ 'name': 'Bercak Target',
208
+ 'symptoms': 'Lesi coklat dengan pola cincin target pada daun dan buah',
209
+ 'causes': 'Jamur Corynespora cassiicola',
210
+ 'prevention': 'Jaga sirkulasi udara, hindari penanaman terlalu rapat',
211
+ 'treatment': 'Gunakan fungisida dan hindari penanaman rapat',
212
+ 'severity': 'sedang'
213
+ },
214
+ 'Bercak_daun_awal': {
215
+ 'name': 'Bercak Daun Awal',
216
+ 'symptoms': 'Lesi coklat dengan cincin konsentris pada daun, dimulai dari daun bawah',
217
+ 'causes': 'Jamur Alternaria solani',
218
+ 'prevention': 'Jaga drainase yang baik, hindari stres pada tanaman, mulsa tanah',
219
+ 'treatment': 'Gunakan fungisida yang mengandung chlorothalonil, buang daun yang terinfeksi',
220
+ 'severity': 'sedang'
221
+ },
222
+ 'Busuk_daun_lanjut': {
223
+ 'name': 'Busuk Daun Lanjut',
224
+ 'symptoms': 'Bercak berair yang menjadi coklat pada daun dan batang, bulu putih di bawah daun',
225
+ 'causes': 'Oomycete Phytophthora infestans',
226
+ 'prevention': 'Hindari kelembaban tinggi, sirkulasi udara yang baik, tanam varietas tahan',
227
+ 'treatment': 'Gunakan fungisida sistemik seperti metalaxyl, hancurkan tanaman yang terinfeksi',
228
+ 'severity': 'tinggi'
229
+ },
230
+ 'Embun_tepung': {
231
+ 'name': 'Embun Tepung',
232
+ 'symptoms': 'Lapisan putih seperti tepung pada permukaan daun',
233
+ 'causes': 'Jamur Leveillula atau Oidium',
234
+ 'prevention': 'Jaga sirkulasi udara, hindari kelembaban',
235
+ 'treatment': 'Gunakan fungisida sulfur atau potassium bicarbonate',
236
+ 'severity': 'sedang'
237
+ },
238
+ 'Jamur_daun': {
239
+ 'name': 'Jamur Daun',
240
+ 'symptoms': 'Bercak kuning pada permukaan atas daun, lapisan fuzzy hijau-abu di bawah daun',
241
+ 'causes': 'Jamur Passalora fulva',
242
+ 'prevention': 'Tingkatkan sirkulasi udara, kurangi kelembaban, jaga jarak tanam',
243
+ 'treatment': 'Tingkatkan sirkulasi udara dan gunakan fungisida yang sesuai',
244
+ 'severity': 'sedang'
245
+ },
246
+ 'Sehat': {
247
+ 'name': 'Tanaman Sehat',
248
+ 'symptoms': 'Daun hijau segar tanpa bercak',
249
+ 'causes': 'Tidak ada penyakit',
250
+ 'prevention': 'Pertahankan kondisi optimal',
251
+ 'treatment': 'Tanaman sehat, lanjutkan perawatan optimal',
252
+ 'severity': 'tidak ada'
253
+ },
254
+ 'Tungau_dua_bercak': {
255
+ 'name': 'Tungau Dua Bercak',
256
+ 'symptoms': 'Daun menguning, bintik putih kecil, jaring laba-laba halus',
257
+ 'causes': 'Tungau Tetranychus urticae',
258
+ 'prevention': 'Jaga kelembaban udara, hindari stres kekeringan',
259
+ 'treatment': 'Gunakan mitisida atau sabun insektisida',
260
+ 'severity': 'sedang'
261
+ },
262
+ 'Virus_keriting_daun_kuning': {
263
+ 'name': 'Virus Keriting Daun Kuning',
264
+ 'symptoms': 'Daun menguning, menggulung ke atas, pertumbuhan terhambat',
265
+ 'causes': 'Virus TYLCV oleh kutu kebul',
266
+ 'prevention': 'Kendalikan kutu kebul, gunakan mulsa reflektif',
267
+ 'treatment': 'Tanam varietas tahan, kendalikan kutu kebul',
268
+ 'severity': 'tinggi'
269
+ },
270
+ 'Virus_mosaik_tomat': {
271
+ 'name': 'Virus Mosaik Tomat',
272
+ 'symptoms': 'Pola mosaik hijau terang dan gelap pada daun, daun keriting',
273
+ 'causes': 'Virus TMV yang menular',
274
+ 'prevention': 'Benih bebas virus, sterilisasi alat',
275
+ 'treatment': 'Hancurkan tanaman terinfeksi, sterilisasi alat',
276
+ 'severity': 'tinggi'
277
+ }
278
+ }
279
+
280
+ return info.get(disease_name, {
281
+ 'name': disease_name,
282
+ 'symptoms': 'Informasi tidak tersedia',
283
+ 'causes': 'Tidak diketahui',
284
+ 'prevention': 'Konsultasikan dengan ahli pertanian',
285
+ 'treatment': 'Konsultasikan dengan ahli setempat',
286
+ 'severity': 'unknown'
287
+ })
288
+
289
+ # Add OPTIONS handler for preflight requests
290
+ @app.before_request
291
+ def handle_preflight():
292
+ if request.method == "OPTIONS":
293
+ response = jsonify({})
294
+ response.headers.add("Access-Control-Allow-Origin", "*")
295
+ response.headers.add('Access-Control-Allow-Headers', "*")
296
+ response.headers.add('Access-Control-Allow-Methods', "*")
297
+ return response
298
+
299
+ @app.route('/health', methods=['GET'])
300
+ def health_check():
301
+ """Check API and model status"""
302
+ return jsonify({
303
+ 'success': True,
304
+ 'message': 'API is running',
305
+ 'model_loaded': model is not None,
306
+ 'status': 'healthy' if model else 'model_not_loaded',
307
+ 'model_info': {
308
+ 'input_shape': str(model.input_shape) if model else None,
309
+ 'output_shape': str(model.output_shape) if model else None,
310
+ 'num_classes': len(class_names)
311
+ }
312
+ })
313
+
314
+ @app.route('/predict', methods=['POST'])
315
+ def predict():
316
+ """Classify disease from uploaded image with validation"""
317
+ print("Predict endpoint called")
318
+ print(f"Files in request: {list(request.files.keys())}")
319
+
320
+ if model is None:
321
+ print("Model not loaded")
322
+ return jsonify({'success': False, 'error': 'Model not loaded'}), 500
323
+
324
+ if 'image' not in request.files:
325
+ print("No 'image' key in request.files")
326
+ return jsonify({'success': False, 'error': 'No image provided'}), 400
327
+
328
+ file = request.files['image']
329
+ print(f"File received: {file.filename}")
330
+
331
+ if file.filename == '':
332
+ print("Empty filename")
333
+ return jsonify({'success': False, 'error': 'No image selected'}), 400
334
+
335
+ if not allowed_file(file.filename):
336
+ print(f"Invalid file type: {file.filename}")
337
+ return jsonify({'success': False, 'error': 'Invalid file type'}), 400
338
+
339
+ try:
340
+ print("Processing image...")
341
+ image_bytes = file.read()
342
+ print(f" Image bytes length: {len(image_bytes)}")
343
+
344
+ # Open and validate image
345
+ image = Image.open(io.BytesIO(image_bytes))
346
+ print(f"Original image - Mode: {image.mode}, Size: {image.size}")
347
+
348
+ # STEP 1: Pre-validation - Check if image looks like a tomato leaf (lebih permisif)
349
+ print("Validating if image is a tomato leaf...")
350
+ is_valid_leaf, validation_reasons, leaf_confidence = validate_tomato_leaf_image(image)
351
+
352
+ if not is_valid_leaf:
353
+ print(f"Image validation failed: {validation_reasons}")
354
+ return jsonify({
355
+ 'success': False,
356
+ 'error': 'Gambar yang diupload bukan daun tomat',
357
+ 'details': {
358
+ 'reasons': validation_reasons,
359
+ 'confidence': leaf_confidence,
360
+ 'suggestion': 'Silakan upload gambar daun tomat yang jelas dengan latar belakang yang kontras'
361
+ }
362
+ }), 400
363
+
364
+ print(f"Image validation passed with confidence: {leaf_confidence:.3f}")
365
+
366
+ # STEP 2: Preprocess image for model
367
+ img_array = preprocess_image(image)
368
+ print(f" Preprocessed array shape: {img_array.shape}")
369
+ print(f" Array min/max: {img_array.min():.3f}/{img_array.max():.3f}")
370
+
371
+ # STEP 3: Make prediction
372
+ print("Making prediction...")
373
+ prediction = model.predict(img_array, verbose=0)
374
+ print(f" Raw prediction shape: {prediction.shape}")
375
+ print(f" Raw prediction: {prediction[0]}")
376
+
377
+ # STEP 4: Post-validation - Check model confidence (lebih permisif)
378
+ model_valid, model_reason = validate_with_model_confidence(prediction, confidence_threshold=0.3)
379
+
380
+ if not model_valid:
381
+ print(f"Model validation failed: {model_reason}")
382
+ return jsonify({
383
+ 'success': False,
384
+ 'error': 'Model tidak dapat mengidentifikasi gambar sebagai daun tomat',
385
+ 'details': {
386
+ 'reason': model_reason,
387
+ 'suggestion': 'Pastikan gambar adalah daun tomat yang jelas dan berkualitas baik'
388
+ }
389
+ }), 400
390
+
391
+ # STEP 5: Extract results
392
+ predicted_index = np.argmax(prediction)
393
+ predicted_class = class_names[predicted_index]
394
+ confidence = float(np.max(prediction))
395
+ confidence_percentage = round(confidence * 100, 2)
396
+
397
+ print(f" Predicted index: {predicted_index}")
398
+ print(f" Predicted class: {predicted_class}")
399
+ print(f" Confidence: {confidence_percentage}%")
400
+
401
+ # Get top 3 predictions for debugging
402
+ top_indices = np.argsort(prediction[0])[::-1][:3]
403
+ print(" Top 3 predictions:")
404
+ for i, idx in enumerate(top_indices):
405
+ print(f" {i+1}. {class_names[idx]}: {prediction[0][idx]*100:.2f}%")
406
+
407
+ # Determine if plant is healthy
408
+ is_plant_healthy = is_healthy_plant(predicted_class)
409
+ print(f" Is healthy: {is_plant_healthy}")
410
+
411
+ # Get disease information
412
+ disease_info = get_disease_info(predicted_class)
413
+
414
+ # Convert image to base64 for response
415
+ image_base64 = base64.b64encode(image_bytes).decode('utf-8')
416
+
417
+ print("Prediction successful")
418
+ return jsonify({
419
+ 'success': True,
420
+ 'data': {
421
+ 'classification': {
422
+ 'class': predicted_class,
423
+ 'class_name': disease_info['name'],
424
+ 'confidence': confidence,
425
+ 'confidence_percentage': confidence_percentage,
426
+ 'is_healthy': is_plant_healthy,
427
+ 'predicted_index': int(predicted_index)
428
+ },
429
+ 'disease_info': disease_info,
430
+ 'validation_info': {
431
+ 'leaf_confidence': leaf_confidence,
432
+ 'passed_pre_validation': True,
433
+ 'passed_model_validation': True
434
+ },
435
+ 'debug_info': {
436
+ 'top_predictions': [
437
+ {
438
+ 'class': class_names[idx],
439
+ 'confidence': float(prediction[0][idx]),
440
+ 'percentage': round(float(prediction[0][idx]) * 100, 2)
441
+ }
442
+ for idx in top_indices
443
+ ],
444
+ 'model_input_shape': str(model.input_shape),
445
+ 'preprocessing_applied': 'resnet50_preprocess'
446
+ },
447
+ 'image_base64': image_base64
448
+ }
449
+ })
450
+
451
+ except Exception as e:
452
+ print(f"Prediction error: {str(e)}")
453
+ import traceback
454
+ traceback.print_exc()
455
+ return jsonify({'success': False, 'error': f'Prediction failed: {str(e)}'}), 500
456
+
457
+ @app.route('/test-classes', methods=['GET'])
458
+ def test_classes():
459
+ """Endpoint untuk testing urutan class names"""
460
+ return jsonify({
461
+ 'success': True,
462
+ 'data': {
463
+ 'class_names': class_names,
464
+ 'num_classes': len(class_names),
465
+ 'model_output_shape': str(model.output_shape) if model else None
466
+ }
467
+ })
468
+
469
+ @app.route('/diseases', methods=['GET'])
470
+ def get_diseases_info():
471
+ """Return list of all known diseases and their descriptions"""
472
+ try:
473
+ data = []
474
+ for class_name in class_names:
475
+ data.append({
476
+ 'class': class_name,
477
+ 'info': get_disease_info(class_name)
478
+ })
479
+ return jsonify({'success': True, 'data': data})
480
+ except Exception as e:
481
+ return jsonify({'success': False, 'error': str(e)}), 500
482
+
483
+ if __name__ == '__main__':
484
+ print("Starting Enhanced Tomato Disease Classification API...")
485
+ print(f"Model loaded: {'Yes' if model is not None else 'No'}")
486
+ if model:
487
+ print(f" Model input shape: {model.input_shape}")
488
+ print(f" Model output classes: {len(class_names)}")
489
+ print("Endpoints:")
490
+ print("- GET /health")
491
+ print("- POST /predict (with image validation)")
492
+ print("- GET /diseases")
493
+ print("- GET /test-classes")
494
+ print("Image validation features:")
495
+ print("- Color analysis (green dominance)")
496
+ print("- Edge structure detection")
497
+ print("- Aspect ratio validation")
498
+ print("- Brightness/contrast checks")
499
+ print("- Model confidence validation")
500
+ print("Server starting on http://0.0.0.0:7860")
501
+ app.run(host='0.0.0.0', port=7860, debug=True)
model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:462111902253f3cb65d54071236ce3a39a1eddc6ba49b4d4c84919149dbb730d
3
+ size 229230144
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask
2
+ Flask-Cors
3
+ numpy
4
+ Pillow
5
+ tensorflow
6
+ opencv-python