aevnum commited on
Commit
97892c7
·
verified ·
1 Parent(s): cefb6c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -71
app.py CHANGED
@@ -5,18 +5,16 @@ import logging
5
  from flask import Flask, request, jsonify, render_template, send_from_directory, url_for
6
  from werkzeug.utils import secure_filename
7
  from ultralytics import YOLO
8
- import google.generativeai as genai
9
  import cv2
10
  from huggingface_hub import hf_hub_download
11
- import io
12
- import tempfile
13
 
14
  # --- Basic Setup & Configuration ---
15
  logging.basicConfig(level=logging.INFO)
16
  app = Flask(__name__)
17
- app.config['UPLOAD_FOLDER'] = 'uploads' # Save uploads in the persistent uploads folder
18
- app.config['RESULT_FOLDER'] = 'static/results' # Save result images in static/results
19
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB file upload limit
20
  ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
21
 
22
  # --- Ensure Folders Exist ---
@@ -42,35 +40,63 @@ if not bird_data:
42
  logging.warning("Bird data is empty. Features relying on it might not work.")
43
 
44
  # --- Load YOLOv8 Model ---
45
- model = None
 
46
  try:
47
- HF_REPO_ID = "aevnum/avian-intelligence-yolo"
 
48
  HF_FILENAME = "best.pt"
49
- MODEL_CACHE_DIR = "/tmp/model_cache"
50
 
 
51
  os.makedirs(MODEL_CACHE_DIR, exist_ok=True)
52
 
53
  logging.info(f"Downloading model {HF_FILENAME} from {HF_REPO_ID}...")
 
54
  hf_token = os.environ.get('HUGGING_FACE_HUB_TOKEN')
55
  if not hf_token:
56
- logging.warning("HUGGING_FACE_HUB_TOKEN not set. Download might fail for private repos.")
57
 
58
  downloaded_model_path = hf_hub_download(
59
  repo_id=HF_REPO_ID,
60
  filename=HF_FILENAME,
61
  cache_dir=MODEL_CACHE_DIR,
62
- force_filename=HF_FILENAME,
63
  token=hf_token
64
  )
65
  logging.info(f"Model downloaded to: {downloaded_model_path}")
66
 
 
67
  model = YOLO(downloaded_model_path)
68
  logging.info("YOLOv8 model loaded successfully from downloaded file.")
69
 
70
  except Exception as e:
71
- logging.exception("Error downloading or loading YOLOv8 model from Hugging Face Hub")
72
  model = None
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  # --- Prediction Function ---
75
  def predict_birds(image_path):
76
  if not model:
@@ -85,11 +111,10 @@ def predict_birds(image_path):
85
 
86
  processed_results = results[0]
87
 
88
- # Save the processed image to the persistent folder (static/results)
89
- result_filename = f"prediction_{uuid.uuid4().hex}.jpg"
90
- result_image_path = os.path.join(app.config['RESULT_FOLDER'], result_filename)
91
- processed_results.save(result_image_path)
92
- logging.info(f"Saved prediction result image to {result_image_path}")
93
 
94
  detected_classes = []
95
  names = processed_results.names
@@ -110,7 +135,8 @@ def predict_birds(image_path):
110
 
111
  detected_classes.sort(key=lambda x: x['confidence'], reverse=True)
112
 
113
- return result_image_path, None, detected_classes
 
114
 
115
  except Exception as e:
116
  logging.exception(f"Error during prediction for {image_path}")
@@ -136,34 +162,45 @@ def handle_prediction():
136
 
137
  if file and allowed_file(file.filename):
138
  filename = secure_filename(file.filename)
139
- # Save the uploaded file to persistent uploads folder
140
- upload_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
141
- file.save(upload_path)
142
- logging.info(f"Uploaded file saved to {upload_path}")
143
-
144
- result_image_path, error_msg, detected_classes = predict_birds(upload_path)
145
-
146
- logging.info(f"predict_birds returned image path: {result_image_path}")
147
-
148
- if error_msg:
149
- logging.error(f"Prediction error message: {error_msg}")
150
- return jsonify({'error': error_msg}), 500
151
-
152
- if result_image_path:
153
- # Construct result image URL for the static folder
154
- result_image_url = url_for('static', filename=f"results/{os.path.basename(result_image_path)}", _external=False)
155
- logging.info(f"Generated result_image_url: {result_image_url}")
156
- else:
157
- result_image_url = None
158
- logging.warning("No result_image_path returned, URL will be null.")
159
-
160
- response_data = {
161
- 'result_image_url': result_image_url,
162
- 'detections': detected_classes
163
- }
164
- logging.info(f"Returning JSON: {response_data}")
165
- return jsonify(response_data)
166
-
 
 
 
 
 
 
 
 
 
 
 
167
  else:
168
  return jsonify({'error': 'Invalid file type'}), 400
169
 
@@ -183,32 +220,47 @@ def get_bird_info(bird_name):
183
  logging.warning(f"Bird info requested for '{safe_bird_name}', but not found in data.")
184
  return jsonify({'error': 'Bird species not found in database'}), 404
185
 
186
- # --- Cleanup after serving ---
187
- # @app.after_request
188
- # def cleanup(response):
189
- # # Clean up the uploaded file after serving
190
- # uploaded_files = os.listdir(app.config['UPLOAD_FOLDER'])
191
- # for filename in uploaded_files:
192
- # file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
193
- # try:
194
- # os.remove(file_path)
195
- # logging.info(f"Removed uploaded file: {file_path}")
196
- # except OSError as e:
197
- # logging.error(f"Error removing uploaded file {file_path}: {e}")
198
-
199
- # # Clean up result images after serving
200
- # result_files = os.listdir(app.config['RESULT_FOLDER'])
201
- # for filename in result_files:
202
- # file_path = os.path.join(app.config['RESULT_FOLDER'], filename)
203
- # try:
204
- # os.remove(file_path)
205
- # logging.info(f"Removed result file: {file_path}")
206
- # except OSError as e:
207
- # logging.error(f"Error removing result file {file_path}: {e}")
208
-
209
- # return response
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  # --- Main Execution ---
212
  if __name__ == '__main__':
213
- port = int(os.environ.get('PORT', 5000)) # Default to 5000 if PORT not set
 
 
214
  app.run(host='0.0.0.0', port=port)
 
5
  from flask import Flask, request, jsonify, render_template, send_from_directory, url_for
6
  from werkzeug.utils import secure_filename
7
  from ultralytics import YOLO
8
+ import google.generativeai as genai # Import Gemini API
9
  import cv2
10
  from huggingface_hub import hf_hub_download
 
 
11
 
12
  # --- Basic Setup & Configuration ---
13
  logging.basicConfig(level=logging.INFO)
14
  app = Flask(__name__)
15
+ app.config['UPLOAD_FOLDER'] = 'uploads'
16
+ app.config['RESULT_FOLDER'] = os.path.join('static', 'results')
17
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
18
  ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
19
 
20
  # --- Ensure Folders Exist ---
 
40
  logging.warning("Bird data is empty. Features relying on it might not work.")
41
 
42
  # --- Load YOLOv8 Model ---
43
+ # --- Download and Load YOLOv8 Model ---
44
+ model = None # Initialize as None
45
  try:
46
+ # Define Hugging Face repo details - CHANGE THESE
47
+ HF_REPO_ID = "aevnum/avian-intelligence-yolo" # <<<--- YOUR HF REPO ID
48
  HF_FILENAME = "best.pt"
49
+ MODEL_CACHE_DIR = "model_cache" # Can be any directory name
50
 
51
+ # Ensure the local model cache directory exists
52
  os.makedirs(MODEL_CACHE_DIR, exist_ok=True)
53
 
54
  logging.info(f"Downloading model {HF_FILENAME} from {HF_REPO_ID}...")
55
+ # Use HF Token from environment variable for download
56
  hf_token = os.environ.get('HUGGING_FACE_HUB_TOKEN')
57
  if not hf_token:
58
+ logging.warning("HUGGING_FACE_HUB_TOKEN not set. Download might fail for private repos or hit rate limits.")
59
 
60
  downloaded_model_path = hf_hub_download(
61
  repo_id=HF_REPO_ID,
62
  filename=HF_FILENAME,
63
  cache_dir=MODEL_CACHE_DIR,
64
+ force_filename=HF_FILENAME, # Helps ensure consistent naming if cache is used
65
  token=hf_token
66
  )
67
  logging.info(f"Model downloaded to: {downloaded_model_path}")
68
 
69
+ # Load the downloaded model
70
  model = YOLO(downloaded_model_path)
71
  logging.info("YOLOv8 model loaded successfully from downloaded file.")
72
 
73
  except Exception as e:
74
+ logging.exception("Error downloading or loading YOLOv8 model from Hugging Face Hub") # Log full traceback
75
  model = None
76
 
77
+ # --- Configure Gemini API ---
78
+ model_gemini = None # Initialize as None
79
+ try:
80
+ gemini_api_key = os.environ.get('GEMINI_API_KEY') # Get key from environment
81
+ if not gemini_api_key:
82
+ logging.warning("GEMINI_API_KEY environment variable not set. Chat feature will be disabled.")
83
+ else:
84
+ logging.info("Configuring Gemini API...")
85
+ genai.configure(api_key=gemini_api_key)
86
+ # Consider making model name configurable too via env var if needed
87
+ # GEMINI_MODEL = os.environ.get('GEMINI_MODEL_NAME', 'gemini-1.5-flash-latest')
88
+ model_gemini = genai.GenerativeModel('gemini-2.0-flash') # Or use variable
89
+ logging.info(f"Gemini client configured with model.")
90
+
91
+ except Exception as e:
92
+ logging.exception("Failed to initialize Gemini client") # Log full traceback
93
+ model_gemini = None
94
+
95
+ # --- Helper Functions ---
96
+ def allowed_file(filename):
97
+ return '.' in filename and \
98
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
99
+
100
  # --- Prediction Function ---
101
  def predict_birds(image_path):
102
  if not model:
 
111
 
112
  processed_results = results[0]
113
 
114
+ output_filename = f"{uuid.uuid4()}.jpg"
115
+ output_path = os.path.join(app.config['RESULT_FOLDER'], output_filename)
116
+ processed_results.save(filename=output_path)
117
+ logging.info(f"Saved prediction result image to {output_path}")
 
118
 
119
  detected_classes = []
120
  names = processed_results.names
 
135
 
136
  detected_classes.sort(key=lambda x: x['confidence'], reverse=True)
137
 
138
+ relative_output_path = f"results/{output_filename}"
139
+ return relative_output_path, None, detected_classes
140
 
141
  except Exception as e:
142
  logging.exception(f"Error during prediction for {image_path}")
 
162
 
163
  if file and allowed_file(file.filename):
164
  filename = secure_filename(file.filename)
165
+ temp_filename = f"{uuid.uuid4()}_{filename}"
166
+ temp_filepath = os.path.join(app.config['UPLOAD_FOLDER'], temp_filename)
167
+
168
+ try:
169
+ file.save(temp_filepath)
170
+ logging.info(f"Uploaded file saved temporarily to {temp_filepath}")
171
+
172
+ result_image_rel_path, error_msg, detected_classes = predict_birds(temp_filepath)
173
+
174
+ logging.info(f"predict_birds returned relative path: {result_image_rel_path}")
175
+
176
+ if error_msg:
177
+ logging.error(f"Prediction error message: {error_msg}")
178
+ return jsonify({'error': error_msg}), 500
179
+
180
+ if result_image_rel_path:
181
+ result_image_url = url_for('static', filename=result_image_rel_path, _external=False)
182
+ logging.info(f"Generated result_image_url: {result_image_url}")
183
+ else:
184
+ result_image_url = None
185
+ logging.warning("No result_image_rel_path returned, URL will be null.")
186
+
187
+ response_data = {
188
+ 'result_image_url': result_image_url,
189
+ 'detections': detected_classes
190
+ }
191
+ logging.info(f"Returning JSON: {response_data}")
192
+ return jsonify(response_data)
193
+
194
+ except Exception as e:
195
+ logging.exception("Error handling prediction request")
196
+ return jsonify({'error': 'Failed to process image'}), 500
197
+ finally:
198
+ if os.path.exists(temp_filepath):
199
+ try:
200
+ os.remove(temp_filepath)
201
+ logging.info(f"Removed temporary file: {temp_filepath}")
202
+ except OSError as e:
203
+ logging.error(f"Error removing temporary file {temp_filepath}: {e}")
204
  else:
205
  return jsonify({'error': 'Invalid file type'}), 400
206
 
 
220
  logging.warning(f"Bird info requested for '{safe_bird_name}', but not found in data.")
221
  return jsonify({'error': 'Bird species not found in database'}), 404
222
 
223
+ @app.route('/chat', methods=['POST'])
224
+ def handle_chat():
225
+ if not model_gemini:
226
+ return jsonify({'reply': "Sorry, the chat feature is not configured or the API key is missing."}), 503
227
+
228
+ data = request.get_json()
229
+ if not data or 'bird_name' not in data or 'message' not in data:
230
+ return jsonify({'error': 'Missing bird_name or message in request'}), 400
231
+
232
+ bird_name = data['bird_name']
233
+ user_message = data['message']
234
+ chat_history = data.get('history', [])
235
+
236
+ bird_details = bird_data.get(bird_name, {})
237
+ context_summary = f"Genus: {bird_details.get('genus', 'N/A')}, Locations: {bird_details.get('locations', 'N/A')}, Info: {bird_details.get('short_info', 'N/A')}."
238
+
239
+ messages = [
240
+ {"role": "system", "content": f"You are a helpful ornithology assistant specializing in bird information. The user is asking about the '{bird_name}'. Basic info: {context_summary}. Keep answers concise and relevant to birds."},
241
+ ]
242
+ for entry in chat_history[-4:]:
243
+ messages.append({"role": entry["role"], "content": entry["content"]})
244
+
245
+ messages.append({"role": "user", "content": user_message})
246
+
247
+ try:
248
+ logging.info(f"Sending request to Gemini for bird: {bird_name}")
249
+ # Gemini API interaction
250
+ prompt = "\n".join([msg["content"] for msg in messages]) #convert messages to one string.
251
+ response = model_gemini.generate_content(prompt)
252
+ ai_reply = response.text.strip()
253
+ logging.info(f"Received reply from Gemini for bird: {bird_name}")
254
+
255
+ return jsonify({'reply': ai_reply})
256
+
257
+ except Exception as e:
258
+ logging.exception("Unexpected error in chat handler")
259
+ return jsonify({'reply': "Sorry, an unexpected error occurred while contacting the AI assistant."}), 500
260
 
261
  # --- Main Execution ---
262
  if __name__ == '__main__':
263
+ # Use host='0.0.0.0' to be accessible within the container
264
+ # Port is usually set by the deployment platform via PORT env var
265
+ port = int(os.environ.get('PORT', 5000)) # Default to 5000 if PORT not set
266
  app.run(host='0.0.0.0', port=port)