Jacksonnavigator7 commited on
Commit
3ebc6c5
·
verified ·
1 Parent(s): 9a8aba9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +784 -10
app.py CHANGED
@@ -44,7 +44,9 @@ translations = {
44
  "loading": "Processing...",
45
  "low_confidence": "Low confidence prediction. Results may not be accurate.",
46
  "not_a_bird": "The image may not contain a bird. Please upload a clear image of a bird.",
47
- "other_message": "This image is not in our trained dataset or the image may not be of a bird. Please try uploading a different image."
 
 
48
  },
49
  "sw": {
50
  "app_title": "Mtafiti wa Ndege: Utambuzi wa Kiotomatiki kwa Watafiti",
@@ -67,19 +69,21 @@ translations = {
67
  "loading": "Inachakata...",
68
  "low_confidence": "Utabiri wa uhakika mdogo. Matokeo yanaweza kuwa si sahihi.",
69
  "not_a_bird": "Picha inaweza isiwe ya ndege. Tafadhali pakia picha wazi ya ndege.",
70
- "other_message": "Ndege huyu haipatikani katika hifadhidata yetu au picha inaweza isiwe ya ndege. Tafadhali jaribu kupakia picha nyingine."
 
 
71
  }
72
  }
73
 
74
- # Example images configuration
75
- EXAMPLE_IMAGES = [
76
- {"path": "image.jpg", "name": "Example Bird 1"},
77
- {"path": "image1.webp", "name": "Example Bird 2"},
78
- {"path": "image2.jpg", "name": "Example Bird 3"},
79
- {"path": "image3.jpg", "name": "Example Bird 4"}
80
- ]
81
 
82
- def clean_bird_name(name):
83
  """Clean bird name by removing numbers and special characters, and fix formatting"""
84
  # Remove numbers and dots at the beginning
85
  cleaned = re.sub(r'^\d+\.', '', name)
@@ -381,6 +385,776 @@ def create_message_html(message, icon="🔍", language="en"):
381
  """
382
  return html
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  def predict_and_get_info(img, language="en"):
385
  """Predict bird species and get detailed information"""
386
  # Get translations
 
44
  "loading": "Processing...",
45
  "low_confidence": "Low confidence prediction. Results may not be accurate.",
46
  "not_a_bird": "The image may not contain a bird. Please upload a clear image of a bird.",
47
+ "other_message": "This image is not in our trained dataset or the image may not be of a bird. Please try uploading a different image.",
48
+ "example_images": "Example Images",
49
+ "try_example": "Try Example"
50
  },
51
  "sw": {
52
  "app_title": "Mtafiti wa Ndege: Utambuzi wa Kiotomatiki kwa Watafiti",
 
69
  "loading": "Inachakata...",
70
  "low_confidence": "Utabiri wa uhakika mdogo. Matokeo yanaweza kuwa si sahihi.",
71
  "not_a_bird": "Picha inaweza isiwe ya ndege. Tafadhali pakia picha wazi ya ndege.",
72
+ "other_message": "Ndege huyu haipatikani katika hifadhidata yetu au picha inaweza isiwe ya ndege. Tafadhali jaribu kupakia picha nyingine.",
73
+ "example_images": "Mifano ya Picha",
74
+ "try_example": "Jaribu Mfano"
75
  }
76
  }
77
 
78
+ # Example images configuration
79
+ EXAMPLE_IMAGES = [
80
+ {"path": "image.jpg", "name": "Example Bird 1"},
81
+ {"path": "image1.webp", "name": "Example Bird 2"},
82
+ {"path": "image2.jpg", "name": "Example Bird 3"},
83
+ {"path": "image3.jpg", "name": "Example Bird 4"}
84
+ ]
85
 
86
+ def clean_bird_name(name):
87
  """Clean bird name by removing numbers and special characters, and fix formatting"""
88
  # Remove numbers and dots at the beginning
89
  cleaned = re.sub(r'^\d+\.', '', name)
 
385
  """
386
  return html
387
 
388
+ def load_example_image(image_path):
389
+ """Load example image for testing"""
390
+ try:
391
+ if os.path.exists(image_path):
392
+ return Image.open(image_path)
393
+ else:
394
+ print(f"Example image not found: {image_path}")
395
+ return None
396
+ except Exception as e:
397
+ print(f"Error loading example image: {e}")
398
+ return None
399
+
400
+ def predict_and_get_info(img, language="en"):
401
+ """Predict bird species and get detailed information"""
402
+ # Get translations
403
+ t = translations[language]
404
+
405
+ # Check if an image was provided
406
+ if img is None:
407
+ message = t['upload_prompt']
408
+ return None, create_message_html(message, "📷", language), "", ""
409
+
410
+ # Basic check if the image might contain a bird
411
+ if not is_likely_bird_image(img):
412
+ message = t['not_a_bird']
413
+ return None, create_message_html(message, "⚠️", language), "", ""
414
+
415
+ try:
416
+ # Process the image
417
+ img = PILImage.create(img)
418
+
419
+ # Get prediction
420
+ pred, pred_idx, probs = learn.predict(img)
421
+
422
+ # Get top 5 predictions (or all if less than 5)
423
+ num_classes = min(5, len(labels))
424
+ top_indices = probs.argsort(descending=True)[:num_classes]
425
+ top_probs = probs[top_indices]
426
+ top_labels = [labels[i] for i in top_indices]
427
+
428
+ # Format as dictionary with cleaned names for display
429
+ prediction_results = {clean_bird_name(top_labels[i]): float(top_probs[i]) for i in range(num_classes)}
430
+
431
+ # Get top prediction (original format for info retrieval)
432
+ top_bird = str(pred)
433
+ # Also keep a clean version for display
434
+ clean_top_bird = clean_bird_name(top_bird)
435
+
436
+ # Check if the model's confidence is low
437
+ if float(top_probs[0]) < 0.4:
438
+ low_confidence_warning = t['low_confidence']
439
+ else:
440
+ low_confidence_warning = ""
441
+
442
+ # Check if the top prediction is "Other" and has high confidence
443
+ if "other" in clean_top_bird.lower():
444
+ # Create a message informing the user that the bird wasn't recognized
445
+ other_message = t['other_message']
446
+ combined_info = create_message_html(other_message, "🔍", language)
447
+ return prediction_results, combined_info, clean_top_bird, ""
448
+
449
+ # Get habitat locations and Tanzania status in a single call
450
+ habitat_locations, is_in_tanzania = get_bird_habitat_and_status(top_bird)
451
+ habitat_map_html = create_habitat_map(habitat_locations)
452
+
453
+ # Get detailed information about the top predicted bird
454
+ bird_info = get_bird_info(top_bird, is_in_tanzania, language)
455
+ formatted_info = format_bird_info(bird_info, is_in_tanzania, language)
456
+
457
+ # Create combined info with map at the top and properly formatted information
458
+ custom_css = """
459
+ <style>
460
+ .bird-container {
461
+ font-family: Arial, sans-serif;
462
+ padding: 10px;
463
+ }
464
+ .map-container {
465
+ height: 400px;
466
+ width: 100%;
467
+ border: 1px solid #ddd;
468
+ border-radius: 8px;
469
+ overflow: hidden;
470
+ margin-bottom: 20px;
471
+ }
472
+ .info-container {
473
+ line-height: 1.6;
474
+ }
475
+ .info-container h3 {
476
+ margin-top: 20px;
477
+ margin-bottom: 10px;
478
+ color: #2c3e50;
479
+ border-bottom: 1px solid #eee;
480
+ padding-bottom: 5px;
481
+ }
482
+ .info-container p {
483
+ margin-bottom: 10px;
484
+ }
485
+ .alert {
486
+ padding: 10px;
487
+ margin-bottom: 15px;
488
+ border-radius: 4px;
489
+ }
490
+ .alert-warning {
491
+ background-color: #fcf8e3;
492
+ border: 1px solid #faebcc;
493
+ color: #8a6d3b;
494
+ }
495
+ .confidence-warning {
496
+ background-color: #fff3cd;
497
+ color: #856404;
498
+ padding: 8px;
499
+ border-radius: 4px;
500
+ margin-bottom: 15px;
501
+ font-weight: bold;
502
+ }
503
+ </style>
504
+ """
505
+
506
+ # Add low confidence warning if needed
507
+ confidence_warning_html = f'<div class="confidence-warning">{low_confidence_warning}</div>' if low_confidence_warning else ''
508
+
509
+ combined_info = f"""
510
+ {custom_css}
511
+ <div class="bird-container">
512
+ {confidence_warning_html}
513
+ <h2>{t['habitat_map_title']} {clean_top_bird}</h2>
514
+ <div class="map-container">
515
+ {habitat_map_html}
516
+ </div>
517
+
518
+ <div class="info-container">
519
+ <h2>{t['detailed_info_title']}</h2>
520
+ {formatted_info}
521
+ </div>
522
+ </div>
523
+ """
524
+
525
+ return prediction_results, combined_info, clean_top_bird, ""
526
+ except Exception as e:
527
+ error_msg = "Hitilafu katika kuchakata picha" if language == "sw" else "Error processing image"
528
+ return None, create_message_html(f"{error_msg}: {str(e)}", "⚠️", language), "", ""
529
+
530
+ def follow_up_question(question, bird_name, language="en"):
531
+ """Allow researchers to ask follow-up questions about the identified bird"""
532
+ t = translations[language]
533
+
534
+ if not question.strip() or not bird_name:
535
+ 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."
536
+
537
+ # Check cache first
538
+ cache_key = f"{bird_name}_{question}_{language}".replace(" ", "_")[:100] # Limit key length
539
+ cached_answer = load_from_cache("follow_up", cache_key)
540
+ if cached_answer is not None:
541
+ return cached_answer
542
+
543
+ # Adjust language for the prompt
544
+ lang_instruction = ""
545
+ if language == "sw":
546
+ lang_instruction = " Provide your response in Swahili language."
547
+
548
+ prompt = f"""
549
+ The researcher is asking about the {bird_name} bird: "{question}"
550
+
551
+ Provide a detailed, scientific answer focusing on accurate ornithological information.
552
+ If the question relates to Tanzania or climate change impacts, emphasize those aspects in your response.
553
+
554
+ IMPORTANT: Do not repeat basic introductory information about the bird that would have already been provided in a general description.
555
+ Do not start your answer with phrases like "Introduction to the {bird_name}" or similar repetitive headers.
556
+ Directly answer the specific question asked.
557
+
558
+ Format your response in markdown for better readability.{lang_instruction}
559
+ """
560
+
561
+ try:
562
+ chat_completion = client.chat.completions.create(
563
+ messages=[
564
+ {
565
+ "role": "user",
566
+ "content": prompt,
567
+ }
568
+ ],
569
+ model="llama-3.3-70b-versatile",
570
+ )
571
+ response = chat_completion.choices[0].message.content
572
+ # Cache the result
573
+ save_to_cache("follow_up", cache_key, response)
574
+ return response
575
+ except Exception as e:
576
+ error_msg = "Hitilafu katika kupata taarifa" if language == "sw" else "Error fetching information"
577
+ return f"{error_msg}: {str(e)}"
578
+
579
+ # Create the Gradio interface
580
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
581
+ # Current language and bird state
582
+ current_lang = gr.State("en")
583
+ current_bird = gr.State("")
584
+
585
+ # Header with language switcher
586
+ with gr.Row():
587
+ with gr.Column(scale=3):
588
+ title_md = gr.Markdown(f"# {translations['en']['app_title']}")
589
+ with gr.Column(scale=1):
590
+ language_selector = gr.Radio(
591
+ choices=["English", "Kiswahili"],
592
+ label=translations['en']['language_label'],
593
+ value="English"
594
+ )
595
+
596
+ # App description
597
+ description_md = gr.Markdown(f"{translations['en']['app_description']}")
598
+
599
+ # Main identification section
600
+ with gr.Row():
601
+ with gr.Column(scale=1):
602
+ input_image = gr.Image(type="pil", label=translations['en']['upload_label'])
603
+
604
+ # Example images section
605
+ example_header = gr.Markdown(f"### {translations['en']['example_images']}")
606
+
607
+ # Create example image buttons in a grid
608
+ with gr.Row():
609
+ example_buttons = []
610
+ for i, example in enumerate(EXAMPLE_IMAGES):
611
+ btn = gr.Button(
612
+ f"{translations['en']['try_example']} {i+1}",
613
+ variant="secondary",
614
+ size="sm"
615
+ )
616
+ example_buttons.append(btn)
617
+
618
+ submit_btn = gr.Button(translations['en']['identify_button'], variant="primary")
619
+
620
+ with gr.Column(scale=2):
621
+ prediction_output = gr.Label(label=translations['en']['predictions_label'], num_top_classes=5)
622
+ bird_info_output = gr.HTML(label=translations['en']['bird_info_label'])
623
+
624
+ # Clear divider
625
+ gr.Markdown("---")
626
+
627
+ # Follow-up question section with improved UI
628
+ questions_header = gr.Markdown(f"## {translations['en']['research_questions']}")
629
+
630
+ conversation_history = gr.Markdown("")
631
+
632
+ with gr.Row():
633
+ follow_up_input = gr.Textbox(
634
+ label=translations['en']['question_label'],
635
+ placeholder=translations['en']['question_placeholder'],
636
+ lines=2
637
+ )
638
+
639
+ with gr.Row():
640
+ follow_up_btn = gr.Button(translations['en']['submit_question'], variant="primary")
641
+ clear_btn = gr.Button(translations['en']['clear_conversation'])
642
+
643
+ # Functions for event handlers
644
+ def update_conversation(question, bird_name, history, lang):
645
+ t = translations[lang]
646
+
647
+ if not question.strip():
648
+ return history
649
+
650
+ answer = follow_up_question(question, bird_name, lang)
651
+
652
+ # Format the conversation with clear separation
653
+ new_exchange = f"""
654
+ ### {t['question_title']}
655
+ {question}
656
+ ### {t['answer_title']}
657
+ {answer}
658
+ ---
659
+ """
660
+ updated_history = new_exchange + history
661
+ return updated_history
662
+
663
+ def clear_conversation_history():
664
+ return ""
665
+
666
+ def update_language(choice):
667
+ # Convert selection to language code
668
+ lang = "sw" if choice == "Kiswahili" else "en"
669
+ t = translations[lang]
670
+
671
+ # Return updated UI components based on selected language
672
+ return (
673
+ lang,
674
+ f"# {t['app_title']}",
675
+ f"{t['app_description']}",
676
+ t['upload_label'],
677
+ f"### {t['example_images']}",
678
+ t['identify_button'],
679
+ t['predictions_label'],
680
+ t['bird_info_label'],
681
+ f"## {t['research_questions']}",
682
+ t['question_label'],
683
+ t['question_placeholder'],
684
+ t['submit_question'],
685
+ t['clear_conversation'],
686
+ # Update example button labels
687
+ *[f"{t['try_example']} {i+1}" for i in range(len(EXAMPLE_IMAGES))]
688
+ )
689
+
690
+ # Functions to handle example image clicks
691
+ def create_example_handlers():
692
+ """Create handlers for example image buttons"""
693
+ handlers = []
694
+ for example in EXAMPLE_IMAGES:
695
+ def make_handler(path):
696
+ def handler():
697
+ return load_example_image(path)
698
+ return handler
699
+ handlers.append(make_handler(example["path"]))
700
+ return handlers
701
+
702
+ example_handlers = create_example_handlers()
703
+
704
+ # Set up event handlers
705
+ language_selector.change(
706
+ update_language,
707
+ inputs=[language_selector],
708
+ outputs=[
709
+ current_lang,
710
+ title_md,
711
+ description_md,
712
+ input_image,
713
+ example_header,
714
+ submit_btn,
715
+ prediction_output,
716
+ bird_info_output,
717
+ questions_header,
718
+ follow_up_input,
719
+ follow_up_input,
720
+ follow_up_btn,
721
+ clear_btn,
722
+ *example_buttons
723
+ ]
724
+ )
725
+
726
+ # Set up example image button handlers
727
+ for i, (btn, handler) in enumerate(zip(example_buttons, example_handlers)):
728
+ btn.click(
729
+ handler,
730
+ outputs=[input_image]
731
+ )
732
+
733
+ # Add loading state for better UX
734
+ submit_btn.click(
735
+ lambda x, y: (None, create_message_html(translations[y]['loading'], "⏳", y), "", ""),
736
+ inputs=[input_image, current_lang],
737
+ outputs=[prediction_output, bird_info_output, current_bird, conversation_history]
738
+ ).then(
739
+ predict_and_get_info,
740
+ inputs=[input_image, current_lang],
741
+ outputs=[prediction_output, bird_info_output, current_bird, conversation_history]
742
+ )
743
+
744
+ follow_up_btn.click(
745
+ update_conversation,
746
+ inputs=[follow_up_input, current_bird, conversation_history, current_lang],
747
+ outputs=[conversation_history]
748
+ ).then(
749
+ lambda: "",
750
+ outputs=follow_up_input
751
+ )
752
+
753
+ clear_btn.click(
754
+ clear_conversation_history,
755
+ outputs=[conversation_history]
756
+ )
757
+
758
+ # Launch the app
759
+ app.launch(share=True)import os
760
+ import gradio as gr
761
+ import re
762
+ import folium
763
+ from fastai.vision.all import *
764
+ from groq import Groq
765
+ from PIL import Image
766
+ import time
767
+ import json
768
+ from functools import lru_cache
769
+
770
+ # Load the trained model
771
+ learn = load_learner('export.pkl')
772
+ labels = learn.dls.vocab
773
+
774
+ # Initialize Groq client
775
+ client = Groq(
776
+ api_key=os.environ.get("GROQ_API_KEY"),
777
+ )
778
+
779
+ # Cache directory for API responses
780
+ os.makedirs("cache", exist_ok=True)
781
+
782
+ # Language translations
783
+ translations = {
784
+ "en": {
785
+ "app_title": "AvianEye Tanzania",
786
+ "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.",
787
+ "upload_label": "Upload Bird Image",
788
+ "identify_button": "Identify Bird",
789
+ "predictions_label": "Top 5 Predictions",
790
+ "bird_info_label": "Bird Information",
791
+ "research_questions": "Research Questions",
792
+ "question_placeholder": "Example: How has climate change affected this bird's migration pattern?",
793
+ "question_label": "Ask a question about this bird",
794
+ "submit_question": "Submit Question",
795
+ "clear_conversation": "Clear Conversation",
796
+ "upload_prompt": "Please upload an image",
797
+ "question_title": "Question:",
798
+ "answer_title": "Answer:",
799
+ "habitat_map_title": "Natural Habitat Map for",
800
+ "detailed_info_title": "Detailed Information",
801
+ "language_label": "Language / Lugha",
802
+ "loading": "Processing...",
803
+ "low_confidence": "Low confidence prediction. Results may not be accurate.",
804
+ "not_a_bird": "The image may not contain a bird. Please upload a clear image of a bird.",
805
+ "other_message": "This image is not in our trained dataset or the image may not be of a bird. Please try uploading a different image.",
806
+ "example_images": "Example Images",
807
+ "try_example": "Try Example"
808
+ },
809
+ "sw": {
810
+ "app_title": "Mtafiti wa Ndege: Utambuzi wa Kiotomatiki kwa Watafiti",
811
+ "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.",
812
+ "upload_label": "Pakia Picha ya Ndege",
813
+ "identify_button": "Tambua Ndege",
814
+ "predictions_label": "Utabiri Bora 5",
815
+ "bird_info_label": "Taarifa za Ndege",
816
+ "research_questions": "Maswali ya Utafiti",
817
+ "question_placeholder": "Mfano: Je, mabadiliko ya tabianchi yameathiri vipi mfumo wa uhamiaji wa ndege huyu?",
818
+ "question_label": "Uliza swali kuhusu ndege huyu",
819
+ "submit_question": "Wasilisha Swali",
820
+ "clear_conversation": "Futa Mazungumzo",
821
+ "upload_prompt": "Tafadhali pakia picha",
822
+ "question_title": "Swali:",
823
+ "answer_title": "Jibu:",
824
+ "habitat_map_title": "Ramani ya Makazi Asilia ya",
825
+ "detailed_info_title": "Taarifa za Kina",
826
+ "language_label": "Language / Lugha",
827
+ "loading": "Inachakata...",
828
+ "low_confidence": "Utabiri wa uhakika mdogo. Matokeo yanaweza kuwa si sahihi.",
829
+ "not_a_bird": "Picha inaweza isiwe ya ndege. Tafadhali pakia picha wazi ya ndege.",
830
+ "other_message": "Ndege huyu haipatikani katika hifadhidata yetu au picha inaweza isiwe ya ndege. Tafadhali jaribu kupakia picha nyingine.",
831
+ "example_images": "Mifano ya Picha",
832
+ "try_example": "Jaribu Mfano"
833
+ }
834
+ }
835
+
836
+ # Example images configuration
837
+ EXAMPLE_IMAGES = [
838
+ {"path": "image.jpg", "name": "Example Bird 1"},
839
+ {"path": "image1.webp", "name": "Example Bird 2"},
840
+ {"path": "image2.jpg", "name": "Example Bird 3"},
841
+ {"path": "image3.jpg", "name": "Example Bird 4"}
842
+ ]
843
+
844
+ def clean_bird_name(name):
845
+ """Clean bird name by removing numbers and special characters, and fix formatting"""
846
+ # Remove numbers and dots at the beginning
847
+ cleaned = re.sub(r'^\d+\.', '', name)
848
+ # Replace underscores with spaces
849
+ cleaned = cleaned.replace('_', ' ')
850
+ # Remove any remaining special characters
851
+ cleaned = re.sub(r'[^\w\s]', '', cleaned)
852
+ # Fix spacing
853
+ cleaned = ' '.join(cleaned.split())
854
+ return cleaned
855
+
856
+ def get_cache_path(function_name, key):
857
+ """Generate a cache file path"""
858
+ safe_key = re.sub(r'[^\w]', '_', key)
859
+ return f"cache/{function_name}_{safe_key}.json"
860
+
861
+ def save_to_cache(function_name, key, data):
862
+ """Save API response to cache"""
863
+ try:
864
+ cache_path = get_cache_path(function_name, key)
865
+ with open(cache_path, 'w') as f:
866
+ json.dump({"data": data, "timestamp": time.time()}, f)
867
+ except Exception as e:
868
+ print(f"Error saving to cache: {e}")
869
+
870
+ def load_from_cache(function_name, key, max_age=86400): # Default max age: 1 day
871
+ """Load API response from cache if it exists and is not too old"""
872
+ try:
873
+ cache_path = get_cache_path(function_name, key)
874
+ if os.path.exists(cache_path):
875
+ with open(cache_path, 'r') as f:
876
+ cached = json.load(f)
877
+ if time.time() - cached["timestamp"] < max_age:
878
+ return cached["data"]
879
+ except Exception as e:
880
+ print(f"Error loading from cache: {e}")
881
+ return None
882
+
883
+ def is_likely_bird_image(img):
884
+ """Basic check to see if the image might contain a bird"""
885
+ try:
886
+ # Convert to numpy array for analysis
887
+ img_array = np.array(img)
888
+
889
+ # Simple checks that might indicate a bird isn't present:
890
+ # 1. Check if image is too dark or too bright overall
891
+ mean_brightness = np.mean(img_array)
892
+ if mean_brightness < 20 or mean_brightness > 235:
893
+ return False
894
+
895
+ # 2. Check if image has very little color variation (might be a solid background)
896
+ std_dev = np.std(img_array)
897
+ if std_dev < 15:
898
+ return False
899
+
900
+ # 3. If image is very small, it might not be a useful bird photo
901
+ if img_array.shape[0] < 100 or img_array.shape[1] < 100:
902
+ return False
903
+
904
+ return True
905
+ except:
906
+ # If any error occurs during the check, assume it might be a bird
907
+ return True
908
+
909
+ def get_bird_habitat_and_status(bird_name):
910
+ """Get both habitat locations and Tanzania status in a single API call"""
911
+ clean_name = clean_bird_name(bird_name)
912
+
913
+ # Check cache first - use a combined cache key
914
+ cache_key = f"{clean_name}_combined"
915
+ cached_result = load_from_cache("combined_habitat_status", cache_key)
916
+ if cached_result is not None:
917
+ return cached_result["locations"], cached_result["is_in_tanzania"]
918
+
919
+ # Single API call to get both information
920
+ prompt = f"""
921
+ Provide information about the {clean_name} bird in the following JSON format:
922
+
923
+ {{
924
+ "is_native_to_tanzania": true/false,
925
+ "habitat_locations": [
926
+ {{
927
+ "name": "Location name",
928
+ "lat": latitude_number,
929
+ "lon": longitude_number,
930
+ "description": "Brief description of habitat"
931
+ }}
932
+ ]
933
+ }}
934
+
935
+ Instructions:
936
+ 1. Set "is_native_to_tanzania" to true if this bird is native to, endemic to, or commonly found in Tanzania. Set to false if it's not typically found there.
937
+ 2. Provide 3-5 main habitat locations worldwide for this bird species.
938
+ 3. If the bird IS native to Tanzania, include Tanzania or East African locations in the habitat list.
939
+ 4. If the bird is NOT native to Tanzania, do not include any Tanzania locations.
940
+ 5. Return ONLY the JSON response, no additional text.
941
+ """
942
+
943
+ try:
944
+ chat_completion = client.chat.completions.create(
945
+ messages=[
946
+ {
947
+ "role": "user",
948
+ "content": prompt,
949
+ }
950
+ ],
951
+ model="llama-3.3-70b-versatile",
952
+ )
953
+ response = chat_completion.choices[0].message.content
954
+
955
+ # Extract and parse JSON from response
956
+ import json
957
+ import re
958
+
959
+ # Find JSON pattern in response
960
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
961
+ if json_match:
962
+ result = json.loads(json_match.group())
963
+ locations = result.get("habitat_locations", [])
964
+ is_in_tanzania = result.get("is_native_to_tanzania", False)
965
+ else:
966
+ # Fallback if JSON parsing fails
967
+ locations = [
968
+ {"name": "Primary habitat region", "lat": 0, "lon": 0,
969
+ "description": "Could not retrieve specific habitat information for this bird."}
970
+ ]
971
+ is_in_tanzania = False
972
+
973
+ # Ensure we have valid data structure
974
+ if not locations:
975
+ locations = [
976
+ {"name": "Unknown habitat", "lat": 0, "lon": 0,
977
+ "description": "Habitat information not available."}
978
+ ]
979
+
980
+ # Cache the combined result
981
+ combined_result = {
982
+ "locations": locations,
983
+ "is_in_tanzania": is_in_tanzania
984
+ }
985
+ save_to_cache("combined_habitat_status", cache_key, combined_result)
986
+
987
+ return locations, is_in_tanzania
988
+
989
+ except Exception as e:
990
+ print(f"Error in get_bird_habitat_and_status: {e}")
991
+ return [{"name": "Error retrieving data", "lat": 0, "lon": 0,
992
+ "description": "Please try again or check your connection."}], False
993
+
994
+ def create_habitat_map(habitat_locations):
995
+ """Create a folium map with the habitat locations"""
996
+ # Find center point based on valid coordinates
997
+ valid_coords = [(loc.get("lat", 0), loc.get("lon", 0))
998
+ for loc in habitat_locations
999
+ if loc.get("lat", 0) != 0 or loc.get("lon", 0) != 0]
1000
+
1001
+ if valid_coords:
1002
+ # Calculate the average of the coordinates
1003
+ avg_lat = sum(lat for lat, _ in valid_coords) / len(valid_coords)
1004
+ avg_lon = sum(lon for _, lon in valid_coords) / len(valid_coords)
1005
+ # Create map centered on the average coordinates
1006
+ m = folium.Map(location=[avg_lat, avg_lon], zoom_start=3)
1007
+ else:
1008
+ # Default world map if no valid coordinates
1009
+ m = folium.Map(location=[20, 0], zoom_start=2)
1010
+
1011
+ # Add markers for each habitat location
1012
+ for location in habitat_locations:
1013
+ name = location.get("name", "Unknown")
1014
+ lat = location.get("lat", 0)
1015
+ lon = location.get("lon", 0)
1016
+ description = location.get("description", "No description available")
1017
+
1018
+ # Skip invalid coordinates
1019
+ if lat == 0 and lon == 0:
1020
+ continue
1021
+
1022
+ # Add marker
1023
+ folium.Marker(
1024
+ location=[lat, lon],
1025
+ popup=folium.Popup(f"<b>{name}</b><br>{description}", max_width=300),
1026
+ tooltip=name
1027
+ ).add_to(m)
1028
+
1029
+ # Save map to HTML
1030
+ map_html = m._repr_html_()
1031
+ return map_html
1032
+
1033
+ def format_bird_info(raw_info, is_in_tanzania, language="en"):
1034
+ """Improve the formatting of bird information"""
1035
+ # Add proper line breaks between sections and ensure consistent heading levels
1036
+ formatted = raw_info
1037
+
1038
+ # Only add the warning if the bird is NOT in Tanzania
1039
+ if not is_in_tanzania:
1040
+ # Get translation of warning text based on language
1041
+ warning_text = "NOT TYPICALLY FOUND IN TANZANIA"
1042
+ warning_translation = "HAPATIKANI SANA TANZANIA" if language == "sw" else warning_text
1043
+
1044
+ # Add warning at the beginning if not already present
1045
+ if warning_translation not in formatted:
1046
+ formatted = f'<div class="alert alert-warning"><strong>⚠️ {warning_translation}</strong></div>\n\n{formatted}'
1047
+
1048
+ # Replace markdown headings with HTML headings for better control
1049
+ formatted = re.sub(r'#+\s+(.*)', r'<h3>\1</h3>', formatted)
1050
+
1051
+ # Add paragraph tags for better spacing
1052
+ formatted = re.sub(r'\n\*\s+(.*)', r'<p>• \1</p>', formatted)
1053
+ formatted = re.sub(r'\n([^<\n].*)', r'<p>\1</p>', formatted)
1054
+
1055
+ # Remove any duplicate paragraph tags
1056
+ formatted = formatted.replace('<p><p>', '<p>')
1057
+ formatted = formatted.replace('</p></p>', '</p>')
1058
+
1059
+ return formatted
1060
+
1061
+ def get_bird_info(bird_name, is_in_tanzania, language="en"):
1062
+ """Get detailed information about a bird using Groq API with caching"""
1063
+ clean_name = clean_bird_name(bird_name)
1064
+
1065
+ # Check cache first - include Tanzania status in cache key
1066
+ cache_key = f"{clean_name}_{language}_{is_in_tanzania}"
1067
+ cached_info = load_from_cache("bird_info", cache_key)
1068
+ if cached_info is not None:
1069
+ return cached_info
1070
+
1071
+ # Adjust language for the prompt
1072
+ lang_instruction = ""
1073
+ if language == "sw":
1074
+ lang_instruction = " Provide your response in Swahili language."
1075
+
1076
+ # Adjust prompt based on Tanzania status
1077
+ tanzania_instruction = ""
1078
+ if not is_in_tanzania:
1079
+ tanzania_instruction = " Important: This bird is NOT typically found in Tanzania. Explain why its presence might be unusual and focus on its natural range."
1080
+ else:
1081
+ tanzania_instruction = " This bird is native to or commonly found in Tanzania. Include information about its presence in Tanzania and East Africa."
1082
+
1083
+ prompt = f"""
1084
+ Provide detailed information about the {clean_name} bird, including:
1085
+ 1. Physical characteristics and appearance
1086
+ 2. Habitat and distribution
1087
+ 3. Diet and behavior
1088
+ 4. Migration patterns (emphasize if this pattern has changed in recent years due to climate change)
1089
+ 5. Conservation status
1090
+
1091
+ {tanzania_instruction}
1092
+
1093
+ Format your response in markdown for better readability.{lang_instruction}
1094
+ """
1095
+
1096
+ try:
1097
+ chat_completion = client.chat.completions.create(
1098
+ messages=[
1099
+ {
1100
+ "role": "user",
1101
+ "content": prompt,
1102
+ }
1103
+ ],
1104
+ model="llama-3.3-70b-versatile",
1105
+ )
1106
+ response = chat_completion.choices[0].message.content
1107
+ # Cache the result
1108
+ save_to_cache("bird_info", cache_key, response)
1109
+ return response
1110
+ except Exception as e:
1111
+ error_msg = "Hitilafu katika kupata taarifa" if language == "sw" else "Error fetching information"
1112
+ return f"{error_msg}: {str(e)}"
1113
+
1114
+ def create_message_html(message, icon="🔍", language="en"):
1115
+ """Create a styled message container for notifications"""
1116
+ custom_css = """
1117
+ <style>
1118
+ .message-container {
1119
+ font-family: Arial, sans-serif;
1120
+ padding: 20px;
1121
+ background-color: #f8f9fa;
1122
+ border-radius: 8px;
1123
+ text-align: center;
1124
+ margin: 20px 0;
1125
+ }
1126
+ .message-icon {
1127
+ font-size: 48px;
1128
+ margin-bottom: 15px;
1129
+ }
1130
+ .message-text {
1131
+ font-size: 18px;
1132
+ color: #495057;
1133
+ }
1134
+ </style>
1135
+ """
1136
+
1137
+ html = f"""
1138
+ {custom_css}
1139
+ <div class="message-container">
1140
+ <div class="message-icon">{icon}</div>
1141
+ <div class="message-text">{message}</div>
1142
+ </div>
1143
+ """
1144
+ return html
1145
+
1146
+ def load_example_image(image_path):
1147
+ """Load example image for testing"""
1148
+ try:
1149
+ if os.path.exists(image_path):
1150
+ return Image.open(image_path)
1151
+ else:
1152
+ print(f"Example image not found: {image_path}")
1153
+ return None
1154
+ except Exception as e:
1155
+ print(f"Error loading example image: {e}")
1156
+ return None
1157
+
1158
  def predict_and_get_info(img, language="en"):
1159
  """Predict bird species and get detailed information"""
1160
  # Get translations