Emmanuel Frimpong Asante commited on
Commit
6b85e51
·
1 Parent(s): 211868f

update space

Browse files
Files changed (1) hide show
  1. app.py +236 -245
app.py CHANGED
@@ -1,258 +1,249 @@
1
- # File: app.py
2
-
3
- # Import necessary libraries
4
  import os
5
- import logging
6
- from pymongo import MongoClient, errors
7
- from datetime import datetime
8
- from werkzeug.security import generate_password_hash
9
- from services.utils import PoultryFarmBot
10
- from services.chatbot import build_chatbot_interface
 
11
  from transformers import AutoModelForCausalLM, AutoTokenizer
12
- from flask import Flask, render_template, request, redirect, url_for, session
13
- from celery import Celery
14
- import time
15
- import threading
16
- import gunicorn.app.base
17
- from six import iteritems
18
-
19
- # Setup logging for better monitoring
20
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
21
- logger = logging.getLogger(__name__)
22
-
23
- # Flask app setup
24
- app = Flask(__name__)
25
- app.secret_key = os.environ.get("SECRET_KEY", "default_secret_key")
26
 
27
- # Celery configuration
28
- CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0")
29
- app.config['CELERY_BROKER_URL'] = CELERY_BROKER_URL
30
- app.config['CELERY_RESULT_BACKEND'] = CELERY_BROKER_URL
31
- celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
32
- celery.conf.update(app.config)
 
 
33
 
34
- # MongoDB Setup for logging and audit
 
35
  MONGO_URI = os.environ.get("MONGO_URI")
36
- if not MONGO_URI:
37
- logger.error("MONGO_URI is not set in the environment variables.")
38
- raise ValueError("MONGO_URI environment variable is required but not set.")
39
-
40
- # Retry logic for MongoDB connection
41
- max_retries = 3
42
- for attempt in range(max_retries):
43
- try:
44
- logger.info(f"Connecting to MongoDB (Attempt {attempt + 1}/{max_retries}).")
45
- client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000) # Timeout after 5 seconds
46
- client.server_info() # Trigger exception if cannot connect
47
- db = client.poultry_farm # Connect to the 'poultry_farm' database
48
- enquiries_collection = db.enquiries # Collection to store farmer enquiries
49
- users_collection = db.users # Collection to store user credentials
50
- logs_collection = db.logs # Collection to store application logs
51
- logger.info("Successfully connected to MongoDB.")
52
- break
53
- except errors.ServerSelectionTimeoutError as e:
54
- logger.error(f"Failed to connect to MongoDB (Attempt {attempt + 1}/{max_retries}): {e}")
55
- if attempt < max_retries - 1:
56
- time.sleep(5) # Wait for 5 seconds before retrying
57
- else:
58
- logger.critical("Could not connect to MongoDB after multiple attempts. Exiting application.")
59
- raise ConnectionError("Could not connect to MongoDB. Please check the MONGO_URI and ensure the database is running.")
60
-
61
- def log_to_db(level, message):
62
- try:
63
- log_entry = {
64
- "level": level,
65
- "message": message,
66
- "timestamp": datetime.utcnow()
67
- }
68
- logs_collection.insert_one(log_entry)
69
- logger.debug(f"Logged to database: {log_entry}")
70
- except Exception as e:
71
- logger.error(f"Failed to log to database: {e}")
72
-
73
- # Override logger methods to also log to MongoDB
74
- class MongoHandler(logging.Handler):
75
- def emit(self, record):
76
- log_entry = self.format(record)
 
 
 
 
 
 
 
 
 
 
 
 
77
  try:
78
- log_to_db(record.levelname, log_entry)
 
 
 
 
 
 
79
  except Exception as e:
80
- logger.error(f"Failed to emit log to MongoDB: {e}")
81
-
82
- mongo_handler = MongoHandler()
83
- mongo_handler.setLevel(logging.INFO)
84
- formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
85
- mongo_handler.setFormatter(formatter)
86
- logger.addHandler(mongo_handler)
87
-
88
- # Hugging Face token setup
89
- tok = os.environ.get('HF_TOKEN')
90
- if tok:
91
- # Log in to Hugging Face using the token from environment variables
92
- logger.info("Logging in to Hugging Face.")
93
- else:
94
- logger.warning("Hugging Face token not found in environment variables.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  # Initialize the bot instance
97
- logger.info("Initializing PoultryFarmBot instance.")
98
- bot = PoultryFarmBot(db)
99
- logger.info("PoultryFarmBot instance initialized.")
100
-
101
- # Global model and tokenizer variables
102
- model = None
103
- tokenizer = None
104
- model_loading_event = threading.Event()
105
-
106
- # Celery task to load the model and tokenizer
107
- @celery.task
108
- def load_model_and_tokenizer():
109
- global model, tokenizer
110
- try:
111
- logger.info("Loading Llama 3.2 model and tokenizer.")
112
- model_name = "meta-llama/Llama-3.2-3B"
113
- tokenizer = AutoTokenizer.from_pretrained(model_name)
114
- model = AutoModelForCausalLM.from_pretrained(model_name)
115
- if tokenizer.pad_token is None:
116
- logger.info("Adding padding token to tokenizer.")
117
- tokenizer.add_special_tokens({'pad_token': '[PAD]'})
118
- model.resize_token_embeddings(len(tokenizer))
119
- logger.info("Model and tokenizer loaded successfully.")
120
- model_loading_event.set() # Set the event to indicate that the model is loaded
121
- except Exception as e:
122
- logger.error(f"Failed to load Llama 3.2 model or tokenizer: {e}")
123
- model_loading_event.set() # Set the event even if loading fails to prevent indefinite waiting
124
- raise RuntimeError("Could not load the Llama 3.2 model or tokenizer. Please check the configuration.")
125
-
126
- # Authentication function
127
- def authenticate_user(username, password):
128
- """
129
- Authenticate a user with username and password.
130
-
131
- Args:
132
- username (str): Username for authentication.
133
- password (str): Password for authentication.
134
-
135
- Returns:
136
- bool: True if authentication is successful, False otherwise.
137
- """
138
- logger.info(f"Authenticating user: {username}")
139
- user = bot.authenticate_user(username, password)
140
- if user:
141
- logger.info(f"Authentication successful for user: {username}")
142
- return True, user
143
- else:
144
- logger.warning(f"Authentication failed for user: {username}")
145
- return False, None
146
-
147
- # Registration function
148
- def register_user(username, password):
149
- """
150
- Register a new user with username and password.
151
-
152
- Args:
153
- username (str): Username for registration.
154
- password (str): Password for registration.
155
-
156
- Returns:
157
- bool: True if registration is successful, False otherwise.
158
- """
159
- logger.info(f"Registering user: {username}")
160
  try:
161
- if users_collection.find_one({"username": username}):
162
- logger.warning("Username already exists: %s", username)
163
- return False
164
- hashed_password = generate_password_hash(password)
165
- users_collection.insert_one({"username": username, "password": hashed_password})
166
- logger.info("User registered successfully: %s", username)
167
- return True
 
 
 
 
 
 
 
 
 
 
168
  except Exception as e:
169
- logger.error(f"Failed to register user: {e}")
170
- return False
171
-
172
- # Flask routes for AdminLTE authentication
173
- @app.route('/', methods=['GET', 'POST'])
174
- def login():
175
- if request.method == 'POST':
176
- username = request.form['username']
177
- password = request.form['password']
178
- logger.info(f"Login attempt for user: {username}")
179
- success, user = authenticate_user(username, password)
180
- if success:
181
- logger.info("Authentication successful for user: %s", username)
182
- session['username'] = username
183
-
184
- # Start loading the model asynchronously after successful login
185
- logger.info("Starting model loading asynchronously.")
186
- load_model_and_tokenizer.apply_async()
187
-
188
- return redirect(url_for('chatbot'))
189
- else:
190
- logger.warning("Authentication failed for user: %s", username)
191
- return render_template('login.html', error="Invalid username or password.")
192
- return render_template('login.html')
193
-
194
- @app.route('/register', methods=['GET', 'POST'])
195
- def register():
196
- if request.method == 'POST':
197
- username = request.form['username']
198
- password = request.form['password']
199
- logger.info(f"Registration attempt for user: {username}")
200
- success = register_user(username, password)
201
- if success:
202
- logger.info("Registration successful for user: %s", username)
203
- return redirect(url_for('login'))
204
  else:
205
- logger.warning("Registration failed for user: %s", username)
206
- return render_template('register.html', error="Username already exists. Please choose a different one.")
207
- return render_template('register.html')
208
-
209
- @app.route('/chatbot')
210
- def chatbot():
211
- if 'username' not in session:
212
- logger.warning("Access to chatbot attempted without login. Redirecting to login page.")
213
- return redirect(url_for('login'))
214
- username = session['username']
215
-
216
- # Wait until the model is loaded with a timeout mechanism
217
- logger.info("Waiting for model to load.")
218
- if not model_loading_event.wait(timeout=100): # Timeout after 100 seconds
219
- logger.error("Model loading timed out for user: %s", username)
220
- return "Model loading timed out. Please try again later."
221
-
222
- if model is None or tokenizer is None:
223
- logger.error("Model failed to load for user: %s", username)
224
- return "Model failed to load. Please try again later."
225
-
226
- logger.info("Launching Gradio chatbot interface for user: %s", username)
227
- chatbot_interface = build_chatbot_interface({'username': username})
228
- return chatbot_interface.launch(inline=True, share=True)
229
-
230
- # Custom Gunicorn application to run Flask safely in a production environment like Hugging Face Space Docker
231
- class StandaloneApplication(gunicorn.app.base.BaseApplication):
232
- def __init__(self, app, options=None):
233
- self.application = app
234
- self.options = options or {}
235
- super().__init__()
236
-
237
- def load_config(self):
238
- config = {key: value for key, value in iteritems(self.options) if key in self.cfg.settings and value is not None}
239
- for key, value in config.items():
240
- self.cfg.set(key.lower(), value)
241
-
242
- def load(self):
243
- return self.application
244
-
245
- # Launch the Flask application using Gunicorn in a production-safe manner
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  if __name__ == "__main__":
247
- try:
248
- logger.info("Launching Flask server for AdminLTE authentication.")
249
- options = {
250
- 'bind': '%s:%s' % ('0.0.0.0', '5000'),
251
- 'workers': 2, # Set number of worker processes
252
- 'threads': 4, # Set number of threads per worker
253
- 'timeout': 120, # Set timeout for workers
254
- }
255
- StandaloneApplication(app, options).run()
256
- except Exception as e:
257
- logger.error(f"Failed to launch Flask server: {e}")
258
- raise RuntimeError("Could not launch the Flask server. Please check the application setup.")
 
 
 
 
1
  import os
2
+ import tensorflow as tf
3
+ from keras.models import load_model
4
+ import gradio as gr
5
+ import cv2
6
+ import numpy as np
7
+ from huggingface_hub import login
8
+ from pymongo import MongoClient
9
  from transformers import AutoModelForCausalLM, AutoTokenizer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Ensure the Hugging Face token is set
12
+ tok = os.environ.get('HF_Token')
13
+ if tok:
14
+ print("Logging in to Hugging Face with provided token...")
15
+ login(token=tok, add_to_git_credential=True)
16
+ print("Login successful.")
17
+ else:
18
+ print("Warning: Hugging Face token not found in environment variables.")
19
 
20
+ # MongoDB Setup (for inventory, record-keeping, etc.)
21
+ print("Setting up MongoDB connection...")
22
  MONGO_URI = os.environ.get("MONGO_URI")
23
+ client = MongoClient(MONGO_URI)
24
+ db = client.poultry_farm # Database
25
+ print("MongoDB connection established.")
26
+
27
+ # Check GPU availability for TensorFlow
28
+ print("Checking TensorFlow setup...")
29
+ print("TensorFlow version:", tf.__version__)
30
+ print("Eager execution:", tf.executing_eagerly())
31
+ print("TensorFlow GPU Available:", tf.config.list_physical_devices('GPU'))
32
+
33
+ # Set TensorFlow to use mixed precision with available GPU
34
+ from tensorflow.keras import mixed_precision
35
+ if len(tf.config.list_physical_devices('GPU')) > 0:
36
+ # Set mixed precision policy to use float16 for better performance on GPU
37
+ policy = mixed_precision.Policy('mixed_float16')
38
+ mixed_precision.set_global_policy(policy)
39
+ print("Using mixed precision with GPU")
40
+ else:
41
+ print("Using CPU without mixed precision")
42
+
43
+ # Load TensorFlow/Keras models with GPU support if available, otherwise use CPU
44
+ try:
45
+ # Select device based on GPU availability
46
+ device_name = '/GPU:0' if len(tf.config.list_physical_devices('GPU')) > 0 else '/CPU:0'
47
+ print(f"Loading models on {device_name}...")
48
+ with tf.device(device_name):
49
+ # Load the poultry disease detection model
50
+ my_model = load_model('models/disease_model.h5', compile=True)
51
+ print("Disease detection model loaded successfully.")
52
+ # Load the authentication model
53
+ auth_model = load_model('models/auth_model.h5', compile=True)
54
+ print("Authentication model loaded successfully.")
55
+ print(f"Models loaded successfully on {device_name}.")
56
+ except Exception as e:
57
+ print(f"Error loading models: {e}")
58
+
59
+ # Updated Disease names and recommendations based on fecal analysis
60
+ name_disease = {0: 'Coccidiosis', 1: 'Healthy', 2: 'New Castle Disease', 3: 'Salmonella'}
61
+ result = {0: 'Critical', 1: 'No issue', 2: 'Critical', 3: 'Critical'}
62
+ recommend = {
63
+ 0: 'Panadol',
64
+ 1: 'You have no need Medicine',
65
+ 2: 'Percetamol',
66
+ 3: 'Ponston'
67
+ }
68
+
69
+ class PoultryFarmBot:
70
+ def __init__(self):
71
+ self.db = db # MongoDB database for future use
72
+ print("PoultryFarmBot initialized with MongoDB connection.")
73
+
74
+ # Image Preprocessing for Fecal Disease Detection
75
+ def preprocess_image(self, image):
76
  try:
77
+ print("Preprocessing image for disease detection...")
78
+ # Resize the image to match model input size (224x224)
79
+ image_check = cv2.resize(image, (224, 224))
80
+ # Add batch dimension to the image array
81
+ image_check = np.expand_dims(image_check, axis=0)
82
+ print("Image preprocessing successful.")
83
+ return image_check
84
  except Exception as e:
85
+ print(f"Error in image preprocessing: {e}")
86
+ return None
87
+
88
+ # Predict Disease from Fecal Image
89
+ def predict(self, image):
90
+ print("Starting disease prediction...")
91
+ # Preprocess the image before prediction
92
+ image_check = self.preprocess_image(image)
93
+ if image_check is None:
94
+ print("Image preprocessing failed.")
95
+ return "Image preprocessing failed.", None, None, None
96
+
97
+ # Predict using the fecal disease detection model
98
+ try:
99
+ print("Running model prediction...")
100
+ indx = my_model.predict(image_check).argmax()
101
+ print(f"Prediction complete. Predicted index: {indx}")
102
+ name = name_disease.get(indx, "Unknown disease")
103
+ status = result.get(indx, "unknown condition")
104
+ recom = recommend.get(indx, "no recommendation available")
105
+
106
+ # Generate additional information about the disease using Llama 2
107
+ detailed_response = self.generate_disease_response(name, status, recom)
108
+ print("Generated detailed response using Llama 2.")
109
+ return detailed_response, name, status, recom
110
+ except Exception as e:
111
+ print(f"Error during prediction: {e}")
112
+ return "Error during prediction.", None, None, None
113
+
114
+ # Generate a detailed response using Llama 2 for disease information and recommendations
115
+ def generate_disease_response(self, disease_name, status, recommendation):
116
+ print("Generating detailed disease response...")
117
+ # Create a prompt for Llama 3 to generate detailed disease information
118
+ prompt = (
119
+ f"The disease detected is {disease_name}, classified as {status}. "
120
+ f"Recommended action: {recommendation}. "
121
+ f"Here is some information about {disease_name}: causes, symptoms, and treatment methods "
122
+ "to effectively manage this condition on a poultry farm."
123
+ )
124
+ response = llama2_response(prompt)
125
+ # Post-process to remove the prompt if accidentally included in the response
126
+ final_response = response.replace(prompt, "").strip()
127
+ print("Detailed disease response generated.")
128
+ return final_response
129
+
130
+ # Diagnose Disease Using Fecal Image
131
+ def diagnose_disease(self, image):
132
+ print("Diagnosing disease from provided image...")
133
+ # Ensure image is valid and has elements
134
+ if image is not None and image.size > 0:
135
+ return self.predict(image)
136
+ print("Invalid image provided.")
137
+ return "Please provide an image of poultry fecal matter for disease detection.", None, None, None
138
 
139
  # Initialize the bot instance
140
+ print("Initializing PoultryFarmBot instance...")
141
+ bot = PoultryFarmBot()
142
+
143
+ # Load Llama 2 model and tokenizer for text generation
144
+ print("Loading Llama 3.2 model and tokenizer...")
145
+ model_name = "meta-llama/Llama-3.2-1B"
146
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
147
+ model = AutoModelForCausalLM.from_pretrained(model_name)
148
+ print("Llama 3.2 model and tokenizer loaded successfully.")
149
+
150
+ # Set the padding token to EOS token or add a new padding token
151
+ if tokenizer.pad_token is None:
152
+ print("Adding pad token to tokenizer...")
153
+ tokenizer.add_special_tokens({'pad_token': '[PAD]'})
154
+ model.resize_token_embeddings(len(tokenizer))
155
+ print("Pad token added and model resized.")
156
+
157
+ # Define Llama 2 response generation
158
+ def llama2_response(user_input):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  try:
160
+ print("Generating response using Llama 2...")
161
+ # Tokenize user input for the Llama 2 model
162
+ inputs = tokenizer(user_input, return_tensors="pt", truncation=True, max_length=150, padding=True)
163
+ # Generate a response using the Llama 2 model
164
+ outputs = model.generate(
165
+ inputs["input_ids"],
166
+ max_length=150,
167
+ do_sample=True,
168
+ temperature=0.7,
169
+ pad_token_id=tokenizer.pad_token_id, # Use the newly set padding token
170
+ attention_mask=inputs["attention_mask"]
171
+ )
172
+
173
+ # Decode and return the response
174
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
175
+ print("Response generated successfully.")
176
+ return response
177
  except Exception as e:
178
+ print(f"Error generating response: {e}")
179
+ return f"Error generating response: {str(e)}"
180
+
181
+ # Main chatbot function: handles both generative AI and disease detection
182
+ def chatbot_response(image, text):
183
+ print("Received user input for chatbot response...")
184
+ # If an image is provided, perform disease detection
185
+ if image is not None:
186
+ print("Image provided, attempting disease diagnosis...")
187
+ diagnosis, name, status, recom = bot.diagnose_disease(image)
188
+ if name and status and recom:
189
+ print("Diagnosis complete, returning detailed response.")
190
+ return diagnosis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  else:
192
+ print("Diagnosis failed or incomplete, returning diagnostic message only.")
193
+ return diagnosis # Return only the diagnostic message if no disease found
194
+ else:
195
+ print("No image provided, using Llama 3.2 for text response...")
196
+ # Use Llama 2 for more accurate responses to user text queries
197
+ return llama2_response(text)
198
+
199
+ # Gradio interface styling and layout with ChatGPT-like theme
200
+ print("Setting up Gradio interface...")
201
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", neutral_hue="slate")) as chatbot_interface:
202
+ gr.Markdown("# 🐔 Poultry Management Chatbot")
203
+ gr.Markdown(
204
+ "This chatbot can help you manage your poultry with conversational AI. Upload an image of poultry fecal matter for disease detection or just ask questions!"
205
+ )
206
+
207
+ with gr.Row():
208
+ with gr.Column(scale=1):
209
+ # Image input for poultry feces (optional)
210
+ fecal_image = gr.Image(
211
+ label="Upload Image of Poultry Feces (Optional)",
212
+ type="numpy",
213
+ elem_id="image-upload",
214
+ show_label=True,
215
+ )
216
+ with gr.Column(scale=2):
217
+ # Text input for user questions
218
+ user_input = gr.Textbox(
219
+ label="Type your question or chat with the assistant",
220
+ placeholder="Ask a question about poultry management...",
221
+ lines=3,
222
+ elem_id="user-input",
223
+ )
224
+
225
+ # Output box to display chatbot responses
226
+ output_box = gr.Textbox(
227
+ label="Response",
228
+ placeholder="The response will appear here...",
229
+ interactive=False,
230
+ lines=10,
231
+ elem_id="output-box",
232
+ )
233
+
234
+ # Submit button to trigger response generation
235
+ submit_button = gr.Button(
236
+ "Submit",
237
+ variant="primary",
238
+ elem_id="submit-button"
239
+ )
240
+ submit_button.click(
241
+ fn=chatbot_response,
242
+ inputs=[fecal_image, user_input],
243
+ outputs=[output_box]
244
+ )
245
+
246
+ # Launch the Gradio interface
247
  if __name__ == "__main__":
248
+ print("Launching Gradio interface...")
249
+ chatbot_interface.queue().launch(debug=True)