-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
389 lines (331 loc) · 14.9 KB
/
app.py
File metadata and controls
389 lines (331 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
from flask import Flask, request, jsonify
from flask_cors import CORS
import tensorflow as tf
import numpy as np
from PIL import Image
import io
import requests
import h5py
import time
import os
from urllib.parse import urlparse
app = Flask(__name__)
CORS(app) # Habilitar CORS para todas las rutas
def is_valid_h5_file(filepath):
"""Verifica si un archivo es un archivo HDF5 válido."""
try:
with h5py.File(filepath, 'r') as f:
return True
except (OSError, IOError):
return False
def download_file_with_retry(url, filepath, max_retries=3, retry_delay=5):
"""Descarga un archivo con reintentos en caso de error."""
for attempt in range(max_retries):
try:
print(f"Intento {attempt + 1} de {max_retries} - Descargando modelo...")
response = requests.get(url, stream=True, timeout=300)
response.raise_for_status()
# Guardar el archivo temporalmente
temp_path = f"{filepath}.temp"
with open(temp_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
# Verificar que el archivo sea un HDF5 válido
if is_valid_h5_file(temp_path):
# Si es válido, renombrar al archivo final
if os.path.exists(filepath):
os.remove(filepath)
os.rename(temp_path, filepath)
print("Modelo descargado y verificado exitosamente")
return True
else:
print("Error: El archivo descargado no es un modelo HDF5 válido")
os.remove(temp_path)
except Exception as e:
print(f"Error en el intento {attempt + 1}: {str(e)}")
if attempt < max_retries - 1:
print(f"Reintentando en {retry_delay} segundos...")
time.sleep(retry_delay)
return False
def ensure_model_exists():
model_path = 'modeloCNN.h5'
MODEL_URL = os.getenv('MODEL_URL', 'https://drive.google.com/uc?export=download&id=1GIIgeGYzYoNd4C0nXo7wfpDLE3-3TjfT')
# Si el archivo existe, verificar que sea válido
if os.path.exists(model_path):
if is_valid_h5_file(model_path):
print("Modelo encontrado y verificado")
return True
else:
print("El archivo del modelo existe pero está corrupto. Intentando descargar de nuevo...")
os.remove(model_path)
# Si llegamos aquí, necesitamos descargar el modelo
print("Iniciando descarga del modelo...")
success = download_file_with_retry(MODEL_URL, model_path)
if not success:
raise RuntimeError("No se pudo descargar un modelo válido después de varios intentos.")
# Inicializar el modelo
try:
ensure_model_exists()
print("Cargando modelo...")
model = tf.keras.models.load_model('modeloCNN.h5')
print("Modelo cargado exitosamente")
except Exception as e:
print(f"Error al cargar el modelo: {str(e)}")
# Etiquetas
labels = [
'Anthracnose', 'Bacterial_Blight', 'Black_Spot', 'Canker', 'Curl_Virus',
'Deficiency', 'Dry_Leaf', 'Greening', 'Healthy', 'Sooty_Mould', 'Spider_Mites'
]
# Recomendaciones de tratamiento para cada enfermedad
recommendations = {
'Anthracnose': [
'Aplicar fungicidas preventivos como clorotalonil o mancozeb',
'Mejorar la circulación de aire entre las plantas',
'Eliminar y destruir hojas infectadas',
'Evitar riego por aspersión, usar riego por goteo',
'Aplicar tratamientos cada 7-10 días durante la temporada húmeda'
],
'Bacterial_Blight': [
'Aplicar antibióticos bacterianos como estreptomicina',
'Podar ramas infectadas y desinfectar herramientas',
'Mejorar el drenaje del suelo para evitar humedad excesiva',
'Usar variedades resistentes cuando sea posible',
'Aplicar cobre bactericida como medida preventiva'
],
'Black_Spot': [
'Aplicar fungicidas sistémicos como tebuconazol',
'Eliminar hojas caídas y restos vegetales',
'Mejorar la ventilación y espaciado entre plantas',
'Evitar humedad excesiva en el follaje',
'Aplicar tratamientos preventivos antes de la temporada de lluvias'
],
'Canker': [
'Aplicar cobre bactericida como oxicloruro de cobre',
'Podar áreas infectadas y desinfectar herramientas',
'Controlar insectos vectores que transmiten la enfermedad',
'Usar material vegetal certificado libre de enfermedades',
'Aplicar tratamientos preventivos en épocas de crecimiento activo'
],
'Curl_Virus': [
'Eliminar plantas infectadas inmediatamente',
'Controlar insectos vectores con insecticidas sistémicos',
'Usar material vegetal sano y certificado',
'Implementar barreras físicas para proteger las plantas',
'Monitorear regularmente para detectar síntomas tempranos'
],
'Deficiency': [
'Aplicar fertilizantes balanceados según análisis de suelo',
'Corregir pH del suelo si es necesario (6.0-7.0)',
'Mejorar nutrición mineral con micronutrientes',
'Realizar análisis de suelo para identificar deficiencias específicas',
'Aplicar fertilizantes foliares para absorción rápida'
],
'Dry_Leaf': [
'Ajustar programa de riego según necesidades de la planta',
'Mejorar drenaje del suelo para evitar encharcamiento',
'Proteger plantas de vientos secos y fuertes',
'Aplicar mulch orgánico para retener humedad',
'Monitorear humedad del suelo regularmente'
],
'Greening': [
'Eliminar plantas infectadas para evitar propagación',
'Controlar psílidos vectores con insecticidas sistémicos',
'Usar material vegetal certificado libre de enfermedades',
'Implementar programas de monitoreo regular',
'Aplicar tratamientos preventivos en épocas de crecimiento'
],
'Healthy': [
'Mantener prácticas de cultivo saludables',
'Monitorear regularmente para detectar problemas tempranos',
'Aplicar fertilizantes balanceados según necesidades',
'Mantener riego adecuado y drenaje apropiado',
'Implementar manejo integrado de plagas'
],
'Sooty_Mould': [
'Controlar insectos productores de melaza (áfidos, escamas)',
'Aplicar jabones insecticidas para eliminar insectos',
'Mejorar la ventilación entre plantas',
'Lavar hojas con agua jabonosa para eliminar melaza',
'Aplicar aceites hortícolas como medida preventiva'
],
'Spider_Mites': [
'Aplicar acaricidas específicos como abamectina',
'Mejorar la humedad ambiental para desfavorecer ácaros',
'Usar depredadores naturales como ácaros fitoseidos',
'Aplicar aceites hortícolas para control mecánico',
'Monitorear regularmente para detectar infestaciones tempranas'
]
}
# Formatos de imagen soportados
SUPPORTED_FORMATS = ['JPEG', 'JPG', 'PNG', 'BMP', 'TIFF', 'WEBP']
def preprocess_image(image_bytes):
try:
img = Image.open(io.BytesIO(image_bytes))
# Convertir a RGB
if img.mode != 'RGB':
img = img.convert('RGB')
img = img.resize((224, 224))
arr = np.array(img) / 255.0
arr = np.expand_dims(arr, axis=0)
return arr
except Exception as e:
raise ValueError(f"Error procesando imagen: {str(e)}")
@app.route('/predict', methods=['POST'])
def predict():
try:
if 'file' not in request.files:
return jsonify({'error': 'No file uploaded'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Verificar formato de archivo
filename = file.filename.lower()
if not any(filename.endswith(fmt.lower()) for fmt in SUPPORTED_FORMATS):
return jsonify({
'error': f'Formato no soportado. Formatos válidos: {", ".join(SUPPORTED_FORMATS)}'
}), 400
img_bytes = file.read()
# Verificar que es una imagen válida
try:
img = Image.open(io.BytesIO(img_bytes))
img.verify() # Verificar que es una imagen válida
except Exception:
return jsonify({'error': 'Archivo no es una imagen válida'}), 400
input_arr = preprocess_image(img_bytes)
preds = model.predict(input_arr)[0]
idx = int(np.argmax(preds))
confidence = float(preds[idx])
label = labels[idx]
return jsonify({
'label': label,
'confidence': confidence,
'all': preds.tolist(),
'filename': file.filename,
'format': img.format if hasattr(img, 'format') else 'Unknown',
'recommendations': recommendations.get(label, [])
})
except Exception as e:
return jsonify({'error': f'Error procesando imagen: {str(e)}'}), 500
@app.route('/predict-url', methods=['POST'])
def predict_url():
try:
data = request.get_json()
if not data or 'url' not in data:
return jsonify({'error': 'No URL provided'}), 400
url = data['url'].strip()
# Validar URL
try:
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
raise ValueError("Invalid URL format")
# Verificar que sea HTTP o HTTPS
if parsed_url.scheme not in ['http', 'https']:
return jsonify({'error': 'Only HTTP and HTTPS URLs are supported'}), 400
except Exception:
return jsonify({'error': 'Invalid URL format'}), 400
# Descargar imagen desde URL
try:
# Headers para simular un navegador
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, timeout=30, headers=headers, stream=True)
response.raise_for_status()
# Verificar que es una imagen
content_type = response.headers.get('content-type', '').lower()
if not content_type.startswith('image/'):
# Proporcionar mensaje de error más útil
if 'text/html' in content_type:
return jsonify({
'error': 'URL points to a webpage, not an image. Please use a direct link to an image file (ending in .jpg, .png, .jpeg, etc.)'
}), 400
else:
return jsonify({
'error': f'URL does not point to an image. Content-Type: {content_type}. Please use a direct link to an image file.'
}), 400
# Verificar tamaño antes de descargar completamente
content_length = response.headers.get('content-length')
if content_length and int(content_length) > 10 * 1024 * 1024:
return jsonify({'error': 'Image too large. Maximum size: 10MB'}), 400
img_bytes = response.content
# Verificar tamaño final (máximo 10MB)
if len(img_bytes) > 10 * 1024 * 1024:
return jsonify({'error': 'Image too large. Maximum size: 10MB'}), 400
except requests.exceptions.RequestException as e:
return jsonify({'error': f'Error downloading image: {str(e)}'}), 400
# Procesar imagen
try:
img = Image.open(io.BytesIO(img_bytes))
img.verify() # Verificar que es una imagen válida
except Exception:
return jsonify({'error': 'Invalid image format'}), 400
input_arr = preprocess_image(img_bytes)
preds = model.predict(input_arr)[0]
idx = int(np.argmax(preds))
confidence = float(preds[idx])
label = labels[idx]
return jsonify({
'label': label,
'confidence': confidence,
'all': preds.tolist(),
'url': url,
'recommendations': recommendations.get(label, [])
})
except Exception as e:
return jsonify({'error': f'Error processing image from URL: {str(e)}'}), 500
@app.route('/validate-url', methods=['POST'])
def validate_url():
"""Validar si una URL apunta a una imagen válida sin procesarla"""
try:
data = request.get_json()
if not data or 'url' not in data:
return jsonify({'error': 'No URL provided'}), 400
url = data['url'].strip()
# Validar URL
try:
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
raise ValueError("Invalid URL format")
if parsed_url.scheme not in ['http', 'https']:
return jsonify({'error': 'Only HTTP and HTTPS URLs are supported'}), 400
except Exception:
return jsonify({'error': 'Invalid URL format'}), 400
# Verificar URL con HEAD request (más eficiente)
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.head(url, timeout=10, headers=headers)
response.raise_for_status()
content_type = response.headers.get('content-type', '').lower()
content_length = response.headers.get('content-length')
if not content_type.startswith('image/'):
return jsonify({
'valid': False,
'error': f'URL does not point to an image. Content-Type: {content_type}'
})
if content_length and int(content_length) > 10 * 1024 * 1024:
return jsonify({
'valid': False,
'error': 'Image too large. Maximum size: 10MB'
})
return jsonify({
'valid': True,
'content_type': content_type,
'content_length': content_length,
'url': url
})
except requests.exceptions.RequestException as e:
return jsonify({
'valid': False,
'error': f'Error accessing URL: {str(e)}'
})
except Exception as e:
return jsonify({'error': f'Error validating URL: {str(e)}'}), 500
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'OK', 'model_loaded': True})
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000))
app.run(host='0.0.0.0', port=port, debug=False)