jacksonwambali commited on
Commit
d54549f
·
verified ·
1 Parent(s): a989aa6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +13 -522
app.py CHANGED
@@ -1,527 +1,18 @@
1
- import os
2
  import gradio as gr
3
- import re
4
- import folium
5
- from fastai.vision.all import *
6
- from groq import Groq
7
- from PIL import Image
8
- from fasttransform import Pipeline
9
 
 
 
10
 
11
- # Load the trained model
12
- learn = load_learner('export.pkl') # Optionally set safe=True for more security
13
- labels = learn.dls.vocab
 
 
 
14
 
15
- # Initialize Groq client
16
- client = Groq(
17
- api_key=os.environ.get("GROQ_API_KEY"),
18
- )
19
 
20
- # Language translations
21
- translations = {
22
- "en": {
23
- "app_title": "Bird Species Explorer: AI-Powered Identification for Researchers",
24
- "app_description": "🔍 Upload a bird photo to instantly identify species and access comprehensive data on habitats, behaviors, and climate change impacts. A powerful tool for Tanzania-based ornithological research.",
25
- "upload_label": "Upload Bird Image",
26
- "identify_button": "Identify Bird",
27
- "predictions_label": "Top 5 Predictions",
28
- "bird_info_label": "Bird Information",
29
- "research_questions": "Research Questions",
30
- "question_placeholder": "Example: How has climate change affected this bird's migration pattern?",
31
- "question_label": "Ask a question about this bird",
32
- "submit_question": "Submit Question",
33
- "clear_conversation": "Clear Conversation",
34
- "upload_prompt": "Please upload an image",
35
- "question_title": "Question:",
36
- "answer_title": "Answer:",
37
- "habitat_map_title": "Natural Habitat Map for",
38
- "detailed_info_title": "Detailed Information",
39
- "language_label": "Language / Lugha"
40
- },
41
- "sw": {
42
- "app_title": "Mtafiti wa Ndege: Utambuzi wa Kiotomatiki kwa Watafiti",
43
- "app_description": "🔍 Pakia picha ya ndege ili kutambua spishi mara moja na kupata data kamili kuhusu makazi, tabia, na athari za mabadiliko ya tabianchi. Zana yenye nguvu kwa utafiti wa ndege nchini Tanzania.",
44
- "upload_label": "Pakia Picha ya Ndege",
45
- "identify_button": "Tambua Ndege",
46
- "predictions_label": "Utabiri Bora 5",
47
- "bird_info_label": "Taarifa za Ndege",
48
- "research_questions": "Maswali ya Utafiti",
49
- "question_placeholder": "Mfano: Je, mabadiliko ya tabianchi yameathiri vipi mfumo wa uhamiaji wa ndege huyu?",
50
- "question_label": "Uliza swali kuhusu ndege huyu",
51
- "submit_question": "Wasilisha Swali",
52
- "clear_conversation": "Futa Mazungumzo",
53
- "upload_prompt": "Tafadhali pakia picha",
54
- "question_title": "Swali:",
55
- "answer_title": "Jibu:",
56
- "habitat_map_title": "Ramani ya Makazi Asilia ya",
57
- "detailed_info_title": "Taarifa za Kina",
58
- "language_label": "Language / Lugha"
59
- }
60
- }
61
-
62
- def clean_bird_name(name):
63
- """Clean bird name by removing numbers and special characters, and fix formatting"""
64
- # Remove numbers and dots at the beginning
65
- cleaned = re.sub(r'^\d+\.', '', name)
66
- # Replace underscores with spaces
67
- cleaned = cleaned.replace('_', ' ')
68
- # Remove any remaining special characters
69
- cleaned = re.sub(r'[^\w\s]', '', cleaned)
70
- # Fix spacing
71
- cleaned = ' '.join(cleaned.split())
72
- return cleaned
73
-
74
- def get_bird_habitat_map(bird_name, check_tanzania=True):
75
- """Get habitat map locations for the bird using Groq API"""
76
- clean_name = clean_bird_name(bird_name)
77
-
78
- # First check if the bird is endemic to Tanzania
79
- if check_tanzania:
80
- tanzania_check_prompt = f"""
81
- Is the {clean_name} bird native to or commonly found in Tanzania?
82
- Answer with ONLY "yes" or "no".
83
- """
84
-
85
- try:
86
- tanzania_check = client.chat.completions.create(
87
- messages=[{"role": "user", "content": tanzania_check_prompt}],
88
- model="llama-3.3-70b-versatile",
89
- )
90
- is_in_tanzania = "yes" in tanzania_check.choices[0].message.content.lower()
91
- except:
92
- # Default to showing Tanzania if we can't determine
93
- is_in_tanzania = True
94
- else:
95
- is_in_tanzania = True
96
-
97
- # Now get the habitat locations
98
- prompt = f"""
99
- Provide a JSON array of the main habitat locations for the {clean_name} bird in the world.
100
- Return ONLY a JSON array with 3-5 entries, each containing:
101
- 1. "name": Location name
102
- 2. "lat": Latitude (numeric value)
103
- 3. "lon": Longitude (numeric value)
104
- 4. "description": Brief description of why this is a key habitat (2-3 sentences)
105
-
106
- Example format:
107
- [
108
- {{"name": "Example Location", "lat": 12.34, "lon": 56.78, "description": "Brief description"}},
109
- ...
110
- ]
111
-
112
- {'' if is_in_tanzania else 'DO NOT include any locations in Tanzania as this bird is not native to or commonly found there.'}
113
- """
114
-
115
- try:
116
- chat_completion = client.chat.completions.create(
117
- messages=[
118
- {
119
- "role": "user",
120
- "content": prompt,
121
- }
122
- ],
123
- model="llama-3.3-70b-versatile",
124
- )
125
- response = chat_completion.choices[0].message.content
126
-
127
- # Extract JSON from response (in case there's additional text)
128
- import json
129
- import re
130
-
131
- # Find JSON pattern in response
132
- json_match = re.search(r'\[.*\]', response, re.DOTALL)
133
- if json_match:
134
- locations = json.loads(json_match.group())
135
- else:
136
- # Fallback if JSON extraction fails
137
- locations = [
138
- {"name": "Primary habitat region", "lat": 0, "lon": 0,
139
- "description": "Could not retrieve specific habitat information for this bird."}
140
- ]
141
-
142
- return locations, is_in_tanzania
143
-
144
- except Exception as e:
145
- return [{"name": "Error retrieving data", "lat": 0, "lon": 0,
146
- "description": "Please try again or check your connection."}], False
147
-
148
- def create_habitat_map(habitat_locations):
149
- """Create a folium map with the habitat locations"""
150
- # Find center point based on valid coordinates
151
- valid_coords = [(loc.get("lat", 0), loc.get("lon", 0))
152
- for loc in habitat_locations
153
- if loc.get("lat", 0) != 0 or loc.get("lon", 0) != 0]
154
-
155
- if valid_coords:
156
- # Calculate the average of the coordinates
157
- avg_lat = sum(lat for lat, _ in valid_coords) / len(valid_coords)
158
- avg_lon = sum(lon for _, lon in valid_coords) / len(valid_coords)
159
- # Create map centered on the average coordinates
160
- m = folium.Map(location=[avg_lat, avg_lon], zoom_start=3)
161
- else:
162
- # Default world map if no valid coordinates
163
- m = folium.Map(location=[20, 0], zoom_start=2)
164
-
165
- # Add markers for each habitat location
166
- for location in habitat_locations:
167
- name = location.get("name", "Unknown")
168
- lat = location.get("lat", 0)
169
- lon = location.get("lon", 0)
170
- description = location.get("description", "No description available")
171
-
172
- # Skip invalid coordinates
173
- if lat == 0 and lon == 0:
174
- continue
175
-
176
- # Add marker
177
- folium.Marker(
178
- location=[lat, lon],
179
- popup=folium.Popup(f"<b>{name}</b><br>{description}", max_width=300),
180
- tooltip=name
181
- ).add_to(m)
182
-
183
- # Save map to HTML
184
- map_html = m._repr_html_()
185
- return map_html
186
-
187
- def format_bird_info(raw_info, language="en"):
188
- """Improve the formatting of bird information"""
189
- # Add proper line breaks between sections and ensure consistent heading levels
190
- formatted = raw_info
191
-
192
- # Get translation of warning text based on language
193
- warning_text = "NOT TYPICALLY FOUND IN TANZANIA"
194
- warning_translation = "HAPATIKANI SANA TANZANIA" if language == "sw" else warning_text
195
-
196
- # Fix heading levels (make all main sections h3)
197
- formatted = re.sub(r'#+\s+' + warning_text,
198
- f'<div class="alert alert-warning"><strong>⚠️ {warning_translation}</strong></div>',
199
- formatted)
200
-
201
- # Replace markdown headings with HTML headings for better control
202
- formatted = re.sub(r'#+\s+(.*)', r'<h3>\1</h3>', formatted)
203
-
204
- # Add paragraph tags for better spacing
205
- formatted = re.sub(r'\n\*\s+(.*)', r'<p>• \1</p>', formatted)
206
- formatted = re.sub(r'\n([^<\n].*)', r'<p>\1</p>', formatted)
207
-
208
- # Remove any duplicate paragraph tags
209
- formatted = formatted.replace('<p><p>', '<p>')
210
- formatted = formatted.replace('</p></p>', '</p>')
211
-
212
- return formatted
213
-
214
- def get_bird_info(bird_name, language="en"):
215
- """Get detailed information about a bird using Groq API"""
216
- clean_name = clean_bird_name(bird_name)
217
-
218
- # Adjust language for the prompt
219
- lang_instruction = ""
220
- if language == "sw":
221
- lang_instruction = " Provide your response in Swahili language."
222
-
223
- prompt = f"""
224
- Provide detailed information about the {clean_name} bird, including:
225
- 1. Physical characteristics and appearance
226
- 2. Habitat and distribution
227
- 3. Diet and behavior
228
- 4. Migration patterns (emphasize if this pattern has changed in recent years due to climate change)
229
- 5. Conservation status
230
-
231
- If this bird is not commonly found in Tanzania, explicitly flag that this bird is "NOT TYPICALLY FOUND IN TANZANIA" at the beginning of your response and explain why its presence might be unusual.
232
-
233
- Format your response in markdown for better readability.{lang_instruction}
234
- """
235
-
236
- try:
237
- chat_completion = client.chat.completions.create(
238
- messages=[
239
- {
240
- "role": "user",
241
- "content": prompt,
242
- }
243
- ],
244
- model="llama-3.3-70b-versatile",
245
- )
246
- return chat_completion.choices[0].message.content
247
- except Exception as e:
248
- error_msg = "Hitilafu katika kupata taarifa" if language == "sw" else "Error fetching information"
249
- return f"{error_msg}: {str(e)}"
250
-
251
- def predict_and_get_info(img, language="en"):
252
- """Predict bird species and get detailed information"""
253
- # Get translations
254
- t = translations[language]
255
-
256
- # Process the image
257
- img = PILImage.create(img)
258
-
259
- # Get prediction
260
- pred, pred_idx, probs = learn.predict(img)
261
-
262
- # Get top 5 predictions (or all if less than 5)
263
- num_classes = min(5, len(labels))
264
- top_indices = probs.argsort(descending=True)[:num_classes]
265
- top_probs = probs[top_indices]
266
- top_labels = [labels[i] for i in top_indices]
267
-
268
- # Format as dictionary with cleaned names for display
269
- prediction_results = {clean_bird_name(top_labels[i]): float(top_probs[i]) for i in range(num_classes)}
270
-
271
- # Get top prediction (original format for info retrieval)
272
- top_bird = str(pred)
273
- # Also keep a clean version for display
274
- clean_top_bird = clean_bird_name(top_bird)
275
-
276
- # Get habitat locations and create map
277
- habitat_locations, is_in_tanzania = get_bird_habitat_map(top_bird)
278
- habitat_map_html = create_habitat_map(habitat_locations)
279
-
280
- # Get detailed information about the top predicted bird
281
- bird_info = get_bird_info(top_bird, language)
282
- formatted_info = format_bird_info(bird_info, language)
283
-
284
- # Create combined info with map at the top and properly formatted information
285
- custom_css = """
286
- <style>
287
- .bird-container {
288
- font-family: Arial, sans-serif;
289
- padding: 10px;
290
- }
291
- .map-container {
292
- height: 400px;
293
- width: 100%;
294
- border: 1px solid #ddd;
295
- border-radius: 8px;
296
- overflow: hidden;
297
- margin-bottom: 20px;
298
- }
299
- .info-container {
300
- line-height: 1.6;
301
- }
302
- .info-container h3 {
303
- margin-top: 20px;
304
- margin-bottom: 10px;
305
- color: #2c3e50;
306
- border-bottom: 1px solid #eee;
307
- padding-bottom: 5px;
308
- }
309
- .info-container p {
310
- margin-bottom: 10px;
311
- }
312
- .alert {
313
- padding: 10px;
314
- margin-bottom: 15px;
315
- border-radius: 4px;
316
- }
317
- .alert-warning {
318
- background-color: #fcf8e3;
319
- border: 1px solid #faebcc;
320
- color: #8a6d3b;
321
- }
322
- </style>
323
- """
324
-
325
- combined_info = f"""
326
- {custom_css}
327
- <div class="bird-container">
328
- <h2>{t['habitat_map_title']} {clean_top_bird}</h2>
329
- <div class="map-container">
330
- {habitat_map_html}
331
- </div>
332
-
333
- <div class="info-container">
334
- <h2>{t['detailed_info_title']}</h2>
335
- {formatted_info}
336
- </div>
337
- </div>
338
- """
339
-
340
- return prediction_results, combined_info, clean_top_bird
341
-
342
- def follow_up_question(question, bird_name, language="en"):
343
- """Allow researchers to ask follow-up questions about the identified bird"""
344
- t = translations[language]
345
-
346
- if not question.strip() or not bird_name:
347
- return "Please identify a bird first and ask a specific question about it." if language == "en" else "Tafadhali tambua ndege kwanza na uulize swali maalum kuhusu ndege huyo."
348
-
349
- # Adjust language for the prompt
350
- lang_instruction = ""
351
- if language == "sw":
352
- lang_instruction = " Provide your response in Swahili language."
353
-
354
- prompt = f"""
355
- The researcher is asking about the {bird_name} bird: "{question}"
356
-
357
- Provide a detailed, scientific answer focusing on accurate ornithological information.
358
- If the question relates to Tanzania or climate change impacts, emphasize those aspects in your response.
359
-
360
- IMPORTANT: Do not repeat basic introductory information about the bird that would have already been provided in a general description.
361
- Do not start your answer with phrases like "Introduction to the {bird_name}" or similar repetitive headers.
362
- Directly answer the specific question asked.
363
-
364
- Format your response in markdown for better readability.{lang_instruction}
365
- """
366
-
367
- try:
368
- chat_completion = client.chat.completions.create(
369
- messages=[
370
- {
371
- "role": "user",
372
- "content": prompt,
373
- }
374
- ],
375
- model="llama-3.3-70b-versatile",
376
- )
377
- return chat_completion.choices[0].message.content
378
- except Exception as e:
379
- error_msg = "Hitilafu katika kupata taarifa" if language == "sw" else "Error fetching information"
380
- return f"{error_msg}: {str(e)}"
381
-
382
- # Create the Gradio interface
383
- with gr.Blocks(theme=gr.themes.Soft()) as app:
384
- # Current language and bird state
385
- current_lang = gr.State("en")
386
- current_bird = gr.State("")
387
-
388
- # Header with language switcher
389
- with gr.Row():
390
- with gr.Column(scale=3):
391
- title_md = gr.Markdown(f"# {translations['en']['app_title']}")
392
- with gr.Column(scale=1):
393
- language_selector = gr.Radio(
394
- choices=["English", "Kiswahili"],
395
- label=translations['en']['language_label'],
396
- value="English"
397
- )
398
-
399
- # App description
400
- description_md = gr.Markdown(f"{translations['en']['app_description']}")
401
-
402
- # Main identification section
403
- with gr.Row():
404
- with gr.Column(scale=1):
405
- input_image = gr.Image(type="pil", label=translations['en']['upload_label'])
406
- submit_btn = gr.Button(translations['en']['identify_button'], variant="primary")
407
-
408
- with gr.Column(scale=2):
409
- prediction_output = gr.Label(label=translations['en']['predictions_label'], num_top_classes=5)
410
- bird_info_output = gr.HTML(label=translations['en']['bird_info_label'])
411
-
412
- # Clear divider
413
- gr.Markdown("---")
414
-
415
- # Follow-up question section with improved UI
416
- questions_header = gr.Markdown(f"## {translations['en']['research_questions']}")
417
-
418
- conversation_history = gr.Markdown("")
419
-
420
- with gr.Row():
421
- follow_up_input = gr.Textbox(
422
- label=translations['en']['question_label'],
423
- placeholder=translations['en']['question_placeholder'],
424
- lines=2
425
- )
426
-
427
- with gr.Row():
428
- follow_up_btn = gr.Button(translations['en']['submit_question'], variant="primary")
429
- clear_btn = gr.Button(translations['en']['clear_conversation'])
430
-
431
- # Functions for event handlers
432
- def process_image(img, lang):
433
- if img is None:
434
- return None, translations[lang]['upload_prompt'], "", ""
435
-
436
- try:
437
- pred_results, info, clean_bird_name = predict_and_get_info(img, lang)
438
- return pred_results, info, clean_bird_name, ""
439
- except Exception as e:
440
- error_msg = "Hitilafu katika kuchakata picha" if lang == "sw" else "Error processing image"
441
- return None, f"{error_msg}: {str(e)}", "", ""
442
-
443
- def update_conversation(question, bird_name, history, lang):
444
- t = translations[lang]
445
-
446
- if not question.strip():
447
- return history
448
-
449
- answer = follow_up_question(question, bird_name, lang)
450
-
451
- # Format the conversation with clear separation
452
- new_exchange = f"""
453
- ### {t['question_title']}
454
- {question}
455
- ### {t['answer_title']}
456
- {answer}
457
- ---
458
- """
459
- updated_history = new_exchange + history
460
- return updated_history
461
-
462
- def clear_conversation_history():
463
- return ""
464
-
465
- def update_language(choice):
466
- # Convert selection to language code
467
- lang = "sw" if choice == "Kiswahili" else "en"
468
- t = translations[lang]
469
-
470
- # Return updated UI components based on selected language
471
- return (
472
- lang,
473
- f"# {t['app_title']}",
474
- f"{t['app_description']}",
475
- t['upload_label'],
476
- t['identify_button'],
477
- t['predictions_label'],
478
- t['bird_info_label'],
479
- f"## {t['research_questions']}",
480
- t['question_label'],
481
- t['question_placeholder'],
482
- t['submit_question'],
483
- t['clear_conversation']
484
- )
485
-
486
- # Set up event handlers
487
- language_selector.change(
488
- update_language,
489
- inputs=[language_selector],
490
- outputs=[
491
- current_lang,
492
- title_md,
493
- description_md,
494
- input_image,
495
- submit_btn,
496
- prediction_output,
497
- bird_info_output,
498
- questions_header,
499
- follow_up_input,
500
- follow_up_input,
501
- follow_up_btn,
502
- clear_btn
503
- ]
504
- )
505
-
506
- submit_btn.click(
507
- process_image,
508
- inputs=[input_image, current_lang],
509
- outputs=[prediction_output, bird_info_output, current_bird, conversation_history]
510
- )
511
-
512
- follow_up_btn.click(
513
- update_conversation,
514
- inputs=[follow_up_input, current_bird, conversation_history, current_lang],
515
- outputs=[conversation_history]
516
- ).then(
517
- lambda: "",
518
- outputs=follow_up_input
519
- )
520
-
521
- clear_btn.click(
522
- clear_conversation_history,
523
- outputs=[conversation_history]
524
- )
525
-
526
- # Launch the app
527
- app.launch(share=True)
 
 
1
  import gradio as gr
2
+ from fastai.vision.all import load_learner, PILImage
 
 
 
 
 
3
 
4
+ # Load your learner
5
+ learn = load_learner('export.pkl')
6
 
7
+ # Define the prediction function
8
+ def predict(img):
9
+ img = PILImage.create(img) # Convert image into PILImage
10
+ pred, pred_idx, probs = learn.predict(img) # Make prediction
11
+ labels = learn.dls.vocab # Get the labels
12
+ return {labels[i]: float(probs[i]) for i in range(len(labels))}
13
 
14
+ # Define the Gradio interface
15
+ interface = gr.Interface(fn=predict, inputs=gr.Image(), outputs=gr.Label(num_top_classes=3))
 
 
16
 
17
+ # Launch the interface
18
+ interface.launch(share=True)