DawnC commited on
Commit
1c3b621
โ€ข
1 Parent(s): 66d751d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +765 -416
app.py CHANGED
@@ -15,34 +15,34 @@ import asyncio
15
  import traceback
16
 
17
 
18
- model_yolo = YOLO('yolov8l.pt')
19
-
20
-
21
- dog_breeds = ["Afghan_Hound", "African_Hunting_Dog", "Airedale", "American_Staffordshire_Terrier",
22
- "Appenzeller", "Australian_Terrier", "Bedlington_Terrier", "Bernese_Mountain_Dog",
23
- "Blenheim_Spaniel", "Border_Collie", "Border_Terrier", "Boston_Bull", "Bouvier_Des_Flandres",
24
- "Brabancon_Griffon", "Brittany_Spaniel", "Cardigan", "Chesapeake_Bay_Retriever",
25
- "Chihuahua", "Dandie_Dinmont", "Doberman", "English_Foxhound", "English_Setter",
26
- "English_Springer", "EntleBucher", "Eskimo_Dog", "French_Bulldog", "German_Shepherd",
27
- "German_Short-Haired_Pointer", "Gordon_Setter", "Great_Dane", "Great_Pyrenees",
28
- "Greater_Swiss_Mountain_Dog", "Ibizan_Hound", "Irish_Setter", "Irish_Terrier",
29
- "Irish_Water_Spaniel", "Irish_Wolfhound", "Italian_Greyhound", "Japanese_Spaniel",
30
- "Kerry_Blue_Terrier", "Labrador_Retriever", "Lakeland_Terrier", "Leonberg", "Lhasa",
31
- "Maltese_Dog", "Mexican_Hairless", "Newfoundland", "Norfolk_Terrier", "Norwegian_Elkhound",
32
- "Norwich_Terrier", "Old_English_Sheepdog", "Pekinese", "Pembroke", "Pomeranian",
33
- "Rhodesian_Ridgeback", "Rottweiler", "Saint_Bernard", "Saluki", "Samoyed",
34
- "Scotch_Terrier", "Scottish_Deerhound", "Sealyham_Terrier", "Shetland_Sheepdog",
35
- "Shih-Tzu", "Siberian_Husky", "Staffordshire_Bullterrier", "Sussex_Spaniel",
36
- "Tibetan_Mastiff", "Tibetan_Terrier", "Walker_Hound", "Weimaraner",
37
- "Welsh_Springer_Spaniel", "West_Highland_White_Terrier", "Yorkshire_Terrier",
38
- "Affenpinscher", "Basenji", "Basset", "Beagle", "Black-and-Tan_Coonhound", "Bloodhound",
39
- "Bluetick", "Borzoi", "Boxer", "Briard", "Bull_Mastiff", "Cairn", "Chow", "Clumber",
40
- "Cocker_Spaniel", "Collie", "Curly-Coated_Retriever", "Dhole", "Dingo",
41
- "Flat-Coated_Retriever", "Giant_Schnauzer", "Golden_Retriever", "Groenendael", "Keeshond",
42
- "Kelpie", "Komondor", "Kuvasz", "Malamute", "Malinois", "Miniature_Pinscher",
43
- "Miniature_Poodle", "Miniature_Schnauzer", "Otterhound", "Papillon", "Pug", "Redbone",
44
- "Schipperke", "Silky_Terrier", "Soft-Coated_Wheaten_Terrier", "Standard_Poodle",
45
- "Standard_Schnauzer", "Toy_Poodle", "Toy_Terrier", "Vizsla", "Whippet",
46
  "Wire-Haired_Fox_Terrier"]
47
 
48
  class MultiHeadAttention(nn.Module):
@@ -141,30 +141,30 @@ async def predict_single_dog(image):
141
  topk_probs, topk_indices = torch.topk(probabilities, k=3)
142
  top1_prob = topk_probs[0][0].item()
143
  topk_breeds = [dog_breeds[idx.item()] for idx in topk_indices[0]]
144
-
145
  # Calculate relative probabilities for display
146
  raw_probs = [prob.item() for prob in topk_probs[0]]
147
  sum_probs = sum(raw_probs)
148
  relative_probs = [f"{(prob/sum_probs * 100):.2f}%" for prob in raw_probs]
149
-
150
  return top1_prob, topk_breeds, relative_probs
151
-
152
 
153
  async def detect_multiple_dogs(image, conf_threshold=0.3, iou_threshold=0.45):
154
  results = model_yolo(image, conf=conf_threshold, iou=iou_threshold)[0]
155
  dogs = []
156
  boxes = []
157
  for box in results.boxes:
158
- if box.cls == 16: # COCO dataset class for dog is 16
159
  xyxy = box.xyxy[0].tolist()
160
  confidence = box.conf.item()
161
  boxes.append((xyxy, confidence))
162
-
163
  if not boxes:
164
  dogs.append((image, 1.0, [0, 0, image.width, image.height]))
165
  else:
166
  nms_boxes = non_max_suppression(boxes, iou_threshold)
167
-
168
  for box, confidence in nms_boxes:
169
  x1, y1, x2, y2 = box
170
  w, h = x2 - x1, y2 - y1
@@ -174,7 +174,7 @@ async def detect_multiple_dogs(image, conf_threshold=0.3, iou_threshold=0.45):
174
  y2 = min(image.height, y2 + h * 0.05)
175
  cropped_image = image.crop((x1, y1, x2, y2))
176
  dogs.append((cropped_image, confidence, [x1, y1, x2, y2]))
177
-
178
  return dogs
179
 
180
 
@@ -187,24 +187,24 @@ def non_max_suppression(boxes, iou_threshold):
187
  boxes = [box for box in boxes if calculate_iou(current[0], box[0]) < iou_threshold]
188
  return keep
189
 
190
-
191
  def calculate_iou(box1, box2):
192
  x1 = max(box1[0], box2[0])
193
  y1 = max(box1[1], box2[1])
194
  x2 = min(box1[2], box2[2])
195
  y2 = min(box1[3], box2[3])
196
-
197
  intersection = max(0, x2 - x1) * max(0, y2 - y1)
198
  area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
199
  area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
200
-
201
  iou = intersection / float(area1 + area2 - intersection)
202
  return iou
203
 
204
 
205
  async def process_single_dog(image):
206
  top1_prob, topk_breeds, relative_probs = await predict_single_dog(image)
207
-
208
  # Case 1: Low confidence - unclear image or breed not in dataset
209
  if top1_prob < 0.2:
210
  error_message = '''
@@ -225,7 +225,7 @@ async def process_single_dog(image):
225
  return error_message, None, initial_state
226
 
227
  breed = topk_breeds[0]
228
-
229
  # Case 2: High confidence - single breed result
230
  if top1_prob >= 0.45:
231
  description = get_dog_description(breed)
@@ -243,7 +243,7 @@ async def process_single_dog(image):
243
  "is_multi_dog": False
244
  }
245
  return html_content, image, initial_state
246
-
247
  # Case 3: Medium confidence - show top 3 breeds with relative probabilities
248
  else:
249
  breeds_html = ""
@@ -269,7 +269,38 @@ async def process_single_dog(image):
269
  }
270
  return breeds_html, image, initial_state
271
 
272
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  async def predict(image):
274
  if image is None:
275
  return "Please upload an image to start.", None, None
@@ -291,7 +322,7 @@ async def predict(image):
291
  '#A233FF', # ็ดซ่‰ฒ
292
  '#FF3333', # ็ด…่‰ฒ
293
  '#33FFB7', # ้’็ถ ่‰ฒ
294
- '#FFE033' # ้‡‘้ปƒ่‰ฒ
295
  ]
296
  annotated_image = image.copy()
297
  draw = ImageDraw.Draw(annotated_image)
@@ -305,14 +336,14 @@ async def predict(image):
305
 
306
  for i, (cropped_image, detection_confidence, box) in enumerate(dogs):
307
  color = single_dog_color if len(dogs) == 1 else color_list[i % len(color_list)]
308
-
309
  # ๅ„ชๅŒ–ๅœ–็‰‡ไธŠ็š„ๆจ™่จ˜
310
  draw.rectangle(box, outline=color, width=4)
311
  label = f"Dog {i+1}"
312
  label_bbox = draw.textbbox((0, 0), label, font=font)
313
  label_width = label_bbox[2] - label_bbox[0]
314
  label_height = label_bbox[3] - label_bbox[1]
315
-
316
  label_x = box[0] + 5
317
  label_y = box[1] + 5
318
  draw.rectangle(
@@ -322,13 +353,13 @@ async def predict(image):
322
  width=2
323
  )
324
  draw.text((label_x, label_y), label, fill=color, font=font)
325
-
326
  top1_prob, topk_breeds, relative_probs = await predict_single_dog(cropped_image)
327
  combined_confidence = detection_confidence * top1_prob
328
-
329
  # ้–‹ๅง‹่ณ‡่จŠๅก็‰‡
330
  dogs_info += f'<div class="dog-info-card" style="border-left: 6px solid {color};">'
331
-
332
  if combined_confidence < 0.2:
333
  dogs_info += f'''
334
  <div class="dog-info-header" style="background-color: {color}10;">
@@ -387,7 +418,7 @@ async def predict(image):
387
  <span class="value">{description['Lifespan']}</span>
388
  </div>
389
  </div>
390
-
391
  <h2 class="section-title">
392
  <span class="icon">๐Ÿ•</span> TEMPERAMENT & PERSONALITY
393
  </h2>
@@ -403,7 +434,7 @@ async def predict(image):
403
  </span>
404
  </span>
405
  </div>
406
-
407
  <h2 class="section-title">
408
  <span class="icon">๐Ÿ’ช</span> CARE REQUIREMENTS
409
  </h2>
@@ -454,7 +485,7 @@ async def predict(image):
454
  <span class="value">{description['Care Level']}</span>
455
  </div>
456
  </div>
457
-
458
  <h2 class="section-title">
459
  <span class="icon">๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ</span> FAMILY COMPATIBILITY
460
  </h2>
@@ -474,14 +505,14 @@ async def predict(image):
474
  <span class="value">{description['Good with Children']}</span>
475
  </div>
476
  </div>
477
-
478
  <h2 class="section-title">
479
  <span class="icon">๐Ÿ“</span> DESCRIPTION
480
  </h2>
481
  <div class="description-section">
482
  <p>{description.get('Description', '')}</p>
483
  </div>
484
-
485
  <div class="action-section">
486
  <a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button">
487
  <span class="icon">๐ŸŒ</span>
@@ -498,16 +529,16 @@ async def predict(image):
498
  <div class="breed-info">
499
  <div class="model-uncertainty-note">
500
  <span class="icon">โ„น๏ธ</span>
501
- Note: The model is showing some uncertainty in its predictions.
502
  Here are the most likely breeds based on the available visual features.
503
  </div>
504
  <div class="breeds-list">
505
  '''
506
-
507
  for j, (breed, prob) in enumerate(zip(topk_breeds, relative_probs)):
508
  description = get_dog_description(breed)
509
  dogs_info += f'''
510
- <div class="breed-option uncertainty-mode">
511
  <div class="breed-header">
512
  <span class="option-number">Option {j+1}</span>
513
  <span class="breed-name">{breed}</span>
@@ -521,46 +552,220 @@ async def predict(image):
521
  </div>
522
  '''
523
  dogs_info += '</div></div>'
524
-
525
  dogs_info += '</div>'
526
 
527
 
528
  html_output = f"""
529
- <style>
530
- .dog-info-card {{
531
- border: 1px solid #e1e4e8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  margin: 40px 0; /* ๅขžๅŠ ๅก็‰‡้–“่ท */
533
- padding: 0;
534
- border-radius: 12px;
535
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
536
  overflow: hidden;
537
  transition: all 0.3s ease;
538
  background: white;
539
- }}
540
-
541
- .dog-info-card:hover {{
542
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
543
- }}
544
-
545
- .dog-info-header {{
546
  padding: 24px 28px; /* ๅขžๅŠ ๅ…ง่ท */
547
  margin: 0;
548
  font-size: 22px;
549
  font-weight: bold;
550
  border-bottom: 1px solid #e1e4e8;
551
- }}
552
-
553
- .breed-info {{
554
  padding: 28px; /* ๅขžๅŠ ๆ•ด้ซ”ๅ…ง่ท */
555
  line-height: 1.6;
556
- }}
557
-
558
- .section-title {{
559
  font-size: 1.3em;
560
  font-weight: 700;
561
  color: #2c3e50;
562
  margin: 32px 0 20px 0;
563
- padding: 12px 0;
564
  border-bottom: 2px solid #e1e4e8;
565
  text-transform: uppercase;
566
  letter-spacing: 0.5px;
@@ -568,16 +773,16 @@ async def predict(image):
568
  align-items: center;
569
  gap: 8px;
570
  position: relative;
571
- }}
572
-
573
- .icon {{
574
  font-size: 1.2em;
575
  display: inline-flex;
576
  align-items: center;
577
  justify-content: center;
578
- }}
579
-
580
- .info-section, .care-section, .family-section {{
581
  display: flex;
582
  flex-wrap: wrap;
583
  gap: 16px;
@@ -586,9 +791,9 @@ async def predict(image):
586
  background: #f8f9fa;
587
  border-radius: 12px;
588
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
589
- }}
590
-
591
- .info-item {{
592
  background: white; /* ๆ”น็‚บ็™ฝ่‰ฒ่ƒŒๆ™ฏ */
593
  padding: 14px 18px; /* ๅขžๅŠ ๅ…ง่ท */
594
  border-radius: 8px;
@@ -599,30 +804,30 @@ async def predict(image):
599
  border: 1px solid #e1e4e8;
600
  flex: 1 1 auto;
601
  min-width: 200px;
602
- }}
603
-
604
- .label {{
605
  color: #666;
606
  font-weight: 600;
607
  font-size: 1.1rem;
608
- }}
609
-
610
- .value {{
611
  color: #2c3e50;
612
  font-weight: 500;
613
  font-size: 1.1rem;
614
- }}
615
-
616
- .temperament-section {{
617
  background: #f8f9fa;
618
  padding: 20px; /* ๅขžๅŠ ๅ…ง่ท */
619
  border-radius: 12px;
620
  margin-bottom: 28px; /* ๅขžๅŠ ้–“่ท */
621
  color: #444;
622
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
623
- }}
624
-
625
- .description-section {{
626
  background: #f8f9fa;
627
  padding: 24px; /* ๅขžๅŠ ๅ…ง่ท */
628
  border-radius: 12px;
@@ -631,28 +836,27 @@ async def predict(image):
631
  color: #444;
632
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
633
  fontsize: 1.1rem;
634
- }}
635
-
636
- .description-section p {{
637
  margin: 0;
638
  padding: 0;
639
  text-align: justify; /* ๆ–‡ๅญ—ๅ…ฉ็ซฏๅฐ้ฝŠ */
640
  word-wrap: break-word; /* ็ขบไฟ้•ทๅ–ฎๅญ—ๆœƒๆ›่กŒ */
641
  white-space: pre-line; /* ไฟ็•™ๆ›่กŒไฝ†ๅˆไฝต็ฉบ็™ฝ */
642
  max-width: 100%; /* ็ขบไฟไธๆœƒ่ถ…ๅ‡บๅฎนๅ™จ */
643
- }}
644
-
645
- .action-section {{
646
  margin-top: 24px;
647
  text-align: center;
648
- }}
649
-
650
  .akc-button,
651
  .breed-section .akc-link,
652
- .breed-option .akc-link {{
653
  display: inline-flex;
654
  align-items: center;
655
- padding: 14px 28px;
656
  background: linear-gradient(145deg, #00509E, #003F7F);
657
  color: white;
658
  border-radius: 12px; /* ๅขžๅŠ ๅœ“่ง’ */
@@ -661,30 +865,29 @@ async def predict(image):
661
  transition: all 0.3s ease;
662
  font-weight: 600;
663
  font-size: 1.1em;
664
- box-shadow:
665
  0 2px 4px rgba(0,0,0,0.1),
666
  inset 0 1px 1px rgba(255,255,255,0.1);
667
- border: 1px solid rgba(255,255,255,0.1);
668
- }}
669
-
670
  .akc-button:hover,
671
  .breed-section .akc-link:hover,
672
- .breed-option .akc-link:hover {{
673
  background: linear-gradient(145deg, #003F7F, #00509E);
674
- transform: translateY(-2px);
675
  color: white;
676
- box-shadow:
677
- 0 6px 12px rgba(0,0,0,0.2),
678
  inset 0 1px 1px rgba(255,255,255,0.2);
679
- border: 1px solid rgba(255,255,255,0.2);
680
- }}
681
-
682
- .icon {{
683
- font-size: 1.3em;
684
- filter: drop-shadow(0 1px 1px rgba(0,0,0,0.2));
685
- }}
686
-
687
- .warning-message {{
688
  display: flex;
689
  align-items: center;
690
  gap: 8px;
@@ -694,9 +897,9 @@ async def predict(image):
694
  padding: 16px;
695
  background: #fff5f5;
696
  border-radius: 8px;
697
- }}
698
-
699
- .model-uncertainty-note {{
700
  display: flex;
701
  align-items: center;
702
  gap: 12px;
@@ -706,108 +909,103 @@ async def predict(image):
706
  margin-bottom: 20px;
707
  color: #495057;
708
  border-radius: 4px;
709
- }}
710
-
711
- .breeds-list {{
712
  display: flex;
713
  flex-direction: column;
714
  gap: 20px;
715
- }}
716
-
717
- .breed-option {{
718
  background: white;
719
  border: 1px solid #e1e4e8;
720
  border-radius: 8px;
721
  overflow: hidden;
722
- }}
723
-
724
- .breed-header {{
725
  display: flex;
726
  align-items: center;
727
  padding: 16px;
728
  background: #f8f9fa;
729
  gap: 12px;
730
  border-bottom: 1px solid #e1e4e8;
731
- }}
732
-
733
- .option-number {{
734
  font-weight: 600;
735
  color: #666;
736
  padding: 4px 8px;
737
  background: #e1e4e8;
738
  border-radius: 4px;
739
- }}
740
-
741
- .breed-name {{
742
  font-size: 1.5em;
743
  font-weight: bold;
744
  color: #2c3e50;
745
  flex-grow: 1;
746
- }}
747
-
748
- .confidence-badge {{
749
  padding: 4px 12px;
750
  border-radius: 20px;
751
  font-size: 0.9em;
752
  font-weight: 500;
753
- }}
754
-
755
- .breed-content {{
756
  padding: 20px;
757
- }}
758
-
759
- .breed-content li {{
760
  margin-bottom: 8px;
761
  display: flex;
762
  align-items: flex-start; /* ๆ”น็‚บ้ ‚้ƒจๅฐ้ฝŠ */
763
  gap: 8px;
764
  flex-wrap: wrap; /* ๅ…่จฑๅ…งๅฎนๆ›่กŒ */
765
- }}
766
-
767
- .breed-content li strong {{
768
  flex: 0 0 auto; /* ไธ่ฎ“ๆจ™้กŒ็ธฎๆ”พ */
769
  min-width: 100px; /* ็ตฆๆจ™้กŒไธ€ๅ€‹ๅ›บๅฎšๆœ€ๅฐๅฏฌๅบฆ */
770
- }}
771
-
772
- ul {{
773
  padding-left: 0;
774
  margin: 0;
775
  list-style-type: none;
776
- }}
777
-
778
- li {{
779
  margin-bottom: 8px;
780
  display: flex;
781
  align-items: center;
782
  gap: 8px;
783
- }}
784
-
785
- .akc-link {{
786
  color: white;
787
  text-decoration: none;
788
  font-weight: 600;
789
  font-size: 1.1em;
790
  transition: all 0.3s ease;
791
- }}
792
-
793
- .akc-link:hover {{
794
  text-decoration: underline;
795
  color: #D3E3F0;
796
- }}
797
- .tooltip {{
798
  position: relative;
799
  display: inline-flex;
800
  align-items: center;
801
  gap: 4px;
802
  cursor: help;
803
- }}
804
-
805
- .tooltip .tooltip-icon {{
806
  font-size: 14px;
807
  color: #666;
808
- }}
809
-
810
- .tooltip .tooltip-text {{
811
  visibility: hidden;
812
  width: 250px;
813
  background-color: rgba(44, 62, 80, 0.95);
@@ -826,47 +1024,43 @@ async def predict(image):
826
  line-height: 1.3;
827
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
828
  border: 1px solid rgba(255, 255, 255, 0.1)
829
- margin-bottom: 10px;
830
- }}
831
-
832
- .tooltip.tooltip-left .tooltip-text {{
833
- left: 0;
834
  transform: translateX(0);
835
- }}
836
-
837
- .tooltip.tooltip-right .tooltip-text {{
838
  left: auto;
839
  right: 0;
840
  transform: translateX(0);
841
- }}
842
-
843
- .tooltip-text strong {{
844
- color: white !important;
845
- background-color: transparent !important;
846
  display: block; /* ่ฎ“ๆจ™้กŒ็จ็ซ‹ไธ€่กŒ */
847
  margin-bottom: 2px; /* ๅขžๅŠ ๆจ™้กŒไธ‹ๆ–น้–“่ท */
848
  padding-bottom: 2px; /* ๅŠ ๅ…ฅๅฐ้–“่ท */
849
  border-bottom: 1px solid rgba(255,255,255,0.2);
850
- }}
851
-
852
- .tooltip-text {{
853
  font-size: 13px; /* ็จๅพฎ็ธฎๅฐๅญ—้ซ” */
854
- }}
855
-
856
  /* ่ชฟๆ•ดๅˆ—่กจ็ฌฆ่™Ÿๅ’Œๆ–‡ๅญ—็š„้–“่ท */
857
- .tooltip-text ul {{
858
  margin: 0;
859
  padding-left: 15px; /* ๆธ›ๅฐ‘ๅˆ—่กจ็ฌฆ่™Ÿ็š„็ธฎ้€ฒ */
860
- }}
861
-
862
- .tooltip-text li {{
863
  margin-bottom: 1px; /* ๆธ›ๅฐ‘ๅˆ—่กจ้ …็›ฎ้–“็š„้–“่ท */
864
- }}
865
- .tooltip-text br {{
866
  line-height: 1.2; /* ๆธ›ๅฐ‘่กŒ่ท */
867
- }}
868
-
869
- .tooltip .tooltip-text::after {{
870
  content: "";
871
  position: absolute;
872
  top: 100%;
@@ -875,23 +1069,20 @@ async def predict(image):
875
  border-width: 5px;
876
  border-style: solid;
877
  border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
878
- }}
879
-
880
- .tooltip-left .tooltip-text::after {{
881
  left: 20%;
882
- }}
883
-
884
  /* ๅณๅด็ฎญ้ ญ */
885
- .tooltip-right .tooltip-text::after {{
886
  left: 80%;
887
- }}
888
-
889
- .tooltip:hover .tooltip-text {{
890
  visibility: visible;
891
  opacity: 1;
892
- }}
893
-
894
- .tooltip .tooltip-text::after {{
895
  content: "";
896
  position: absolute;
897
  top: 100%;
@@ -900,9 +1091,8 @@ async def predict(image):
900
  border-width: 8px;
901
  border-style: solid;
902
  border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
903
- }}
904
-
905
- .uncertainty-mode .tooltip .tooltip-text {{
906
  position: absolute;
907
  left: 100%;
908
  bottom: auto;
@@ -910,9 +1100,9 @@ async def predict(image):
910
  transform: translateY(-50%);
911
  margin-left: 10px;
912
  z-index: 1000; /* ็ขบไฟๆ็คบๆก†ๅœจๆœ€ไธŠๅฑค */
913
- }}
914
-
915
- .uncertainty-mode .tooltip .tooltip-text::after {{
916
  content: "";
917
  position: absolute;
918
  top: 50%;
@@ -921,220 +1111,379 @@ async def predict(image):
921
  border-width: 5px;
922
  border-style: solid;
923
  border-color: transparent rgba(44, 62, 80, 0.95) transparent transparent;
924
- }}
925
-
926
- .uncertainty-mode .breed-content {{
927
  font-size: 1.1rem; /* ๅขžๅŠ ๅญ—้ซ”ๅคงๅฐ */
928
- }}
929
-
930
  .description-section,
931
  .description-section p,
932
  .temperament-section,
933
  .temperament-section .value,
934
  .info-item,
935
  .info-item .value,
936
- .breed-content {{
937
  font-size: 1.1rem !important; /* ไฝฟ็”จ !important ็ขบไฟ่ฆ†่“‹ๅ…ถไป–ๆจฃๅผ */
938
- }}
939
- </style>
940
- {dogs_info}
941
- """
942
 
943
- initial_state = {
944
- "dogs_info": dogs_info,
945
- "image": annotated_image,
946
- "is_multi_dog": len(dogs) > 1,
947
- "html_output": html_output
948
  }
949
-
950
- return html_output, annotated_image, initial_state
951
 
952
- except Exception as e:
953
- error_msg = f"An error occurred: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
954
- print(error_msg)
955
- return error_msg, None, None
956
-
 
957
 
958
- def show_details_html(choice, previous_output, initial_state):
959
- if not choice:
960
- return previous_output, gr.update(visible=True), initial_state
961
 
962
- try:
963
- breed = choice.split("More about ")[-1]
964
- description = get_dog_description(breed)
965
- formatted_description = format_description_html(description, breed)
966
-
967
- html_output = f"""
968
- <div class="dog-info">
969
- <h2>{breed}</h2>
970
- {formatted_description}
971
- </div>
972
- """
973
-
974
- initial_state["current_description"] = html_output
975
- initial_state["original_buttons"] = initial_state.get("buttons", [])
976
-
977
- return html_output, gr.update(visible=True), initial_state
978
- except Exception as e:
979
- error_msg = f"An error occurred while showing details: {e}"
980
- print(error_msg)
981
- return f"<p style='color: red;'>{error_msg}</p>", gr.update(visible=True), initial_state
982
-
983
 
984
- def format_description_html(description, breed):
985
- html = "<ul style='list-style-type: none; padding-left: 0;'>"
986
- if isinstance(description, dict):
987
- for key, value in description.items():
988
- if key != "Breed": # ่ทณ้Ž้‡่ค‡็š„ๅ“็จฎ้กฏ็คบ
989
- if key == "Size":
990
- html += f'''
991
- <li style='margin-bottom: 10px;'>
992
- <span class="tooltip">
993
- <strong>{key}:</strong>
994
- <span class="tooltip-icon">โ“˜</span>
995
- <span class="tooltip-text">
996
- <strong>Size Categories:</strong><br>
997
- โ€ข Small: Under 20 pounds<br>
998
- โ€ข Medium: 20-60 pounds<br>
999
- โ€ข Large: Over 60 pounds
1000
- </span>
1001
- </span> {value}
1002
- </li>
1003
- '''
1004
- elif key == "Exercise Needs":
1005
- html += f'''
1006
- <li style='margin-bottom: 10px;'>
1007
- <span class="tooltip">
1008
- <strong>{key}:</strong>
1009
- <span class="tooltip-icon">โ“˜</span>
1010
- <span class="tooltip-text">
1011
- <strong>Exercise Needs:</strong><br>
1012
- โ€ข High: 2+ hours of daily exercise<br>
1013
- โ€ข Moderate: 1-2 hours of daily activity<br>
1014
- โ€ข Low: Short walks and play sessions
1015
- </span>
1016
- </span> {value}
1017
- </li>
1018
- '''
1019
- elif key == "Grooming Needs":
1020
- html += f'''
1021
- <li style='margin-bottom: 10px;'>
1022
- <span class="tooltip">
1023
- <strong>{key}:</strong>
1024
- <span class="tooltip-icon">โ“˜</span>
1025
- <span class="tooltip-text">
1026
- <strong>Grooming Requirements:</strong><br>
1027
- โ€ข High: Daily brushing, regular professional care<br>
1028
- โ€ข Moderate: Weekly brushing, occasional grooming<br>
1029
- โ€ข Low: Minimal brushing, basic maintenance
1030
- </span>
1031
- </span> {value}
1032
- </li>
1033
- '''
1034
- elif key == "Care Level":
1035
- html += f'''
1036
- <li style='margin-bottom: 10px;'>
1037
- <span class="tooltip">
1038
- <strong>{key}:</strong>
1039
- <span class="tooltip-icon">โ“˜</span>
1040
- <span class="tooltip-text">
1041
- <strong>Care Level Explained:</strong><br>
1042
- โ€ข High: Needs significant training and attention<br>
1043
- โ€ข Moderate: Regular care and routine needed<br>
1044
- โ€ข Low: More independent, basic care sufficient
1045
- </span>
1046
- </span> {value}
1047
- </li>
1048
- '''
1049
- elif key == "Good with Children":
1050
- html += f'''
1051
- <li style='margin-bottom: 10px;'>
1052
- <span class="tooltip">
1053
- <strong>{key}:</strong>
1054
- <span class="tooltip-icon">โ“˜</span>
1055
- <span class="tooltip-text">
1056
- <strong>Child Compatibility:</strong><br>
1057
- โ€ข Yes: Excellent with kids, patient and gentle<br>
1058
- โ€ข Moderate: Good with older children<br>
1059
- โ€ข No: Better suited for adult households
1060
- </span>
1061
- </span> {value}
1062
- </li>
1063
- '''
1064
- elif key == "Lifespan":
1065
- html += f'''
1066
- <li style='margin-bottom: 10px;'>
1067
- <span class="tooltip">
1068
- <strong>{key}:</strong>
1069
- <span class="tooltip-icon">โ“˜</span>
1070
- <span class="tooltip-text">
1071
- <strong>Average Lifespan:</strong><br>
1072
- โ€ข Short: 6-8 years<br>
1073
- โ€ข Average: 10-15 years<br>
1074
- โ€ข Long: 12-20 years
1075
- </span>
1076
- </span> {value}
1077
- </li>
1078
- '''
1079
- elif key == "Temperament":
1080
- html += f'''
1081
- <li style='margin-bottom: 10px;'>
1082
- <span class="tooltip">
1083
- <strong>{key}:</strong>
1084
- <span class="tooltip-icon">โ“˜</span>
1085
- <span class="tooltip-text">
1086
- <strong>Temperament Guide:</strong><br>
1087
- โ€ข Describes the dog's natural behavior<br>
1088
- โ€ข Important for matching with owner
1089
- </span>
1090
- </span> {value}
1091
- </li>
1092
- '''
1093
- else:
1094
- # ๅ…ถไป–ๆฌ„ไฝไฟๆŒๅŽŸๆจฃ้กฏ็คบ
1095
- html += f"<li style='margin-bottom: 10px;'><strong>{key}:</strong> {value}</li>"
1096
- else:
1097
- html += f"<li>{description}</li>"
1098
- html += "</ul>"
1099
-
1100
- # ๆทปๅŠ AKC้€ฃ็ต
1101
- html += f'''
1102
- <div class="action-section">
1103
- <a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button">
1104
- <span class="icon">๐ŸŒ</span>
1105
- Learn more about {breed} on AKC website
1106
- </a>
1107
- </div>
1108
- '''
1109
- return html
1110
 
 
 
 
 
 
1111
 
1112
- with gr.Blocks() as iface:
1113
- gr.HTML("<h1 style='text-align: center;'>๐Ÿถ Dog Breed Classifier ๐Ÿ”</h1>")
1114
- gr.HTML("<p style='text-align: center;'>Upload a picture of a dog, and the model will predict its breed and provide detailed information!</p>")
1115
- gr.HTML("<p style='text-align: center; color: #666; font-size: 0.9em;'>Note: The model's predictions may not always be 100% accurate, and it is recommended to use the results as a reference.</p>")
1116
-
1117
-
1118
- with gr.Row():
1119
- input_image = gr.Image(label="Upload a dog image", type="pil")
1120
- output_image = gr.Image(label="Annotated Image")
1121
-
1122
- output = gr.HTML(label="Prediction Results")
1123
- initial_state = gr.State()
1124
-
1125
- input_image.change(
1126
- predict,
1127
- inputs=input_image,
1128
- outputs=[output, output_image, initial_state]
1129
- )
1130
-
1131
- gr.Examples(
1132
- examples=['Border_Collie.jpg', 'Golden_Retriever.jpeg', 'Saint_Bernard.jpeg', 'French_Bulldog.jpeg', 'Samoyed.jpg'],
1133
- inputs=input_image
1134
- )
1135
-
1136
- gr.HTML('For more details on this project and other work, feel free to visit my GitHub <a href="https://github.com/Eric-Chung-0511/Learning-Record/tree/main/Data%20Science%20Projects/Dog_Breed_Classifier">Dog Breed Classifier</a>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1138
 
1139
  if __name__ == "__main__":
1140
- iface.launch()
 
 
15
  import traceback
16
 
17
 
18
+ model_yolo = YOLO('yolov8l.pt')
19
+
20
+
21
+ dog_breeds = ["Afghan_Hound", "African_Hunting_Dog", "Airedale", "American_Staffordshire_Terrier",
22
+ "Appenzeller", "Australian_Terrier", "Bedlington_Terrier", "Bernese_Mountain_Dog",
23
+ "Blenheim_Spaniel", "Border_Collie", "Border_Terrier", "Boston_Bull", "Bouvier_Des_Flandres",
24
+ "Brabancon_Griffon", "Brittany_Spaniel", "Cardigan", "Chesapeake_Bay_Retriever",
25
+ "Chihuahua", "Dandie_Dinmont", "Doberman", "English_Foxhound", "English_Setter",
26
+ "English_Springer", "EntleBucher", "Eskimo_Dog", "French_Bulldog", "German_Shepherd",
27
+ "German_Short-Haired_Pointer", "Gordon_Setter", "Great_Dane", "Great_Pyrenees",
28
+ "Greater_Swiss_Mountain_Dog", "Ibizan_Hound", "Irish_Setter", "Irish_Terrier",
29
+ "Irish_Water_Spaniel", "Irish_Wolfhound", "Italian_Greyhound", "Japanese_Spaniel",
30
+ "Kerry_Blue_Terrier", "Labrador_Retriever", "Lakeland_Terrier", "Leonberg", "Lhasa",
31
+ "Maltese_Dog", "Mexican_Hairless", "Newfoundland", "Norfolk_Terrier", "Norwegian_Elkhound",
32
+ "Norwich_Terrier", "Old_English_Sheepdog", "Pekinese", "Pembroke", "Pomeranian",
33
+ "Rhodesian_Ridgeback", "Rottweiler", "Saint_Bernard", "Saluki", "Samoyed",
34
+ "Scotch_Terrier", "Scottish_Deerhound", "Sealyham_Terrier", "Shetland_Sheepdog",
35
+ "Shih-Tzu", "Siberian_Husky", "Staffordshire_Bullterrier", "Sussex_Spaniel",
36
+ "Tibetan_Mastiff", "Tibetan_Terrier", "Walker_Hound", "Weimaraner",
37
+ "Welsh_Springer_Spaniel", "West_Highland_White_Terrier", "Yorkshire_Terrier",
38
+ "Affenpinscher", "Basenji", "Basset", "Beagle", "Black-and-Tan_Coonhound", "Bloodhound",
39
+ "Bluetick", "Borzoi", "Boxer", "Briard", "Bull_Mastiff", "Cairn", "Chow", "Clumber",
40
+ "Cocker_Spaniel", "Collie", "Curly-Coated_Retriever", "Dhole", "Dingo",
41
+ "Flat-Coated_Retriever", "Giant_Schnauzer", "Golden_Retriever", "Groenendael", "Keeshond",
42
+ "Kelpie", "Komondor", "Kuvasz", "Malamute", "Malinois", "Miniature_Pinscher",
43
+ "Miniature_Poodle", "Miniature_Schnauzer", "Otterhound", "Papillon", "Pug", "Redbone",
44
+ "Schipperke", "Silky_Terrier", "Soft-Coated_Wheaten_Terrier", "Standard_Poodle",
45
+ "Standard_Schnauzer", "Toy_Poodle", "Toy_Terrier", "Vizsla", "Whippet",
46
  "Wire-Haired_Fox_Terrier"]
47
 
48
  class MultiHeadAttention(nn.Module):
 
141
  topk_probs, topk_indices = torch.topk(probabilities, k=3)
142
  top1_prob = topk_probs[0][0].item()
143
  topk_breeds = [dog_breeds[idx.item()] for idx in topk_indices[0]]
144
+
145
  # Calculate relative probabilities for display
146
  raw_probs = [prob.item() for prob in topk_probs[0]]
147
  sum_probs = sum(raw_probs)
148
  relative_probs = [f"{(prob/sum_probs * 100):.2f}%" for prob in raw_probs]
149
+
150
  return top1_prob, topk_breeds, relative_probs
151
+
152
 
153
  async def detect_multiple_dogs(image, conf_threshold=0.3, iou_threshold=0.45):
154
  results = model_yolo(image, conf=conf_threshold, iou=iou_threshold)[0]
155
  dogs = []
156
  boxes = []
157
  for box in results.boxes:
158
+ if box.cls == 16: # COCO dataset class for dog is 16
159
  xyxy = box.xyxy[0].tolist()
160
  confidence = box.conf.item()
161
  boxes.append((xyxy, confidence))
162
+
163
  if not boxes:
164
  dogs.append((image, 1.0, [0, 0, image.width, image.height]))
165
  else:
166
  nms_boxes = non_max_suppression(boxes, iou_threshold)
167
+
168
  for box, confidence in nms_boxes:
169
  x1, y1, x2, y2 = box
170
  w, h = x2 - x1, y2 - y1
 
174
  y2 = min(image.height, y2 + h * 0.05)
175
  cropped_image = image.crop((x1, y1, x2, y2))
176
  dogs.append((cropped_image, confidence, [x1, y1, x2, y2]))
177
+
178
  return dogs
179
 
180
 
 
187
  boxes = [box for box in boxes if calculate_iou(current[0], box[0]) < iou_threshold]
188
  return keep
189
 
190
+
191
  def calculate_iou(box1, box2):
192
  x1 = max(box1[0], box2[0])
193
  y1 = max(box1[1], box2[1])
194
  x2 = min(box1[2], box2[2])
195
  y2 = min(box1[3], box2[3])
196
+
197
  intersection = max(0, x2 - x1) * max(0, y2 - y1)
198
  area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
199
  area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
200
+
201
  iou = intersection / float(area1 + area2 - intersection)
202
  return iou
203
 
204
 
205
  async def process_single_dog(image):
206
  top1_prob, topk_breeds, relative_probs = await predict_single_dog(image)
207
+
208
  # Case 1: Low confidence - unclear image or breed not in dataset
209
  if top1_prob < 0.2:
210
  error_message = '''
 
225
  return error_message, None, initial_state
226
 
227
  breed = topk_breeds[0]
228
+
229
  # Case 2: High confidence - single breed result
230
  if top1_prob >= 0.45:
231
  description = get_dog_description(breed)
 
243
  "is_multi_dog": False
244
  }
245
  return html_content, image, initial_state
246
+
247
  # Case 3: Medium confidence - show top 3 breeds with relative probabilities
248
  else:
249
  breeds_html = ""
 
269
  }
270
  return breeds_html, image, initial_state
271
 
272
+
273
+ def create_breed_comparison(breed1: str, breed2: str) -> dict:
274
+ """ๆฏ”่ผƒๅ…ฉๅ€‹็‹—ๅ“็จฎ็š„็‰นๆ€ง"""
275
+ breed1_info = get_dog_description(breed1)
276
+ breed2_info = get_dog_description(breed2)
277
+
278
+ # ๆจ™ๆบ–ๅŒ–ๆ•ธๅ€ผ่ฝ‰ๆ›
279
+ value_mapping = {
280
+ 'Size': {'Small': 1, 'Medium': 2, 'Large': 3, 'Giant': 4},
281
+ 'Exercise_Needs': {'Low': 1, 'Moderate': 2, 'High': 3, 'Very High': 4},
282
+ 'Care_Level': {'Low': 1, 'Moderate': 2, 'High': 3},
283
+ 'Grooming_Needs': {'Low': 1, 'Moderate': 2, 'High': 3}
284
+ }
285
+
286
+ comparison_data = {
287
+ breed1: {},
288
+ breed2: {}
289
+ }
290
+
291
+ for breed, info in [(breed1, breed1_info), (breed2, breed2_info)]:
292
+ comparison_data[breed] = {
293
+ 'Size': value_mapping['Size'].get(info['Size'], 2), # ้ ่จญ Medium
294
+ 'Exercise_Needs': value_mapping['Exercise_Needs'].get(info['Exercise Needs'], 2), # ้ ่จญ Moderate
295
+ 'Care_Level': value_mapping['Care_Level'].get(info['Care Level'], 2),
296
+ 'Grooming_Needs': value_mapping['Grooming_Needs'].get(info['Grooming Needs'], 2),
297
+ 'Good_with_Children': info['Good with Children'] == 'Yes',
298
+ 'Original_Data': info
299
+ }
300
+
301
+ return comparison_data
302
+
303
+
304
  async def predict(image):
305
  if image is None:
306
  return "Please upload an image to start.", None, None
 
322
  '#A233FF', # ็ดซ่‰ฒ
323
  '#FF3333', # ็ด…่‰ฒ
324
  '#33FFB7', # ้’็ถ ่‰ฒ
325
+ '#FFE033' # ้‡‘้ปƒ่‰ฒ
326
  ]
327
  annotated_image = image.copy()
328
  draw = ImageDraw.Draw(annotated_image)
 
336
 
337
  for i, (cropped_image, detection_confidence, box) in enumerate(dogs):
338
  color = single_dog_color if len(dogs) == 1 else color_list[i % len(color_list)]
339
+
340
  # ๅ„ชๅŒ–ๅœ–็‰‡ไธŠ็š„ๆจ™่จ˜
341
  draw.rectangle(box, outline=color, width=4)
342
  label = f"Dog {i+1}"
343
  label_bbox = draw.textbbox((0, 0), label, font=font)
344
  label_width = label_bbox[2] - label_bbox[0]
345
  label_height = label_bbox[3] - label_bbox[1]
346
+
347
  label_x = box[0] + 5
348
  label_y = box[1] + 5
349
  draw.rectangle(
 
353
  width=2
354
  )
355
  draw.text((label_x, label_y), label, fill=color, font=font)
356
+
357
  top1_prob, topk_breeds, relative_probs = await predict_single_dog(cropped_image)
358
  combined_confidence = detection_confidence * top1_prob
359
+
360
  # ้–‹ๅง‹่ณ‡่จŠๅก็‰‡
361
  dogs_info += f'<div class="dog-info-card" style="border-left: 6px solid {color};">'
362
+
363
  if combined_confidence < 0.2:
364
  dogs_info += f'''
365
  <div class="dog-info-header" style="background-color: {color}10;">
 
418
  <span class="value">{description['Lifespan']}</span>
419
  </div>
420
  </div>
421
+
422
  <h2 class="section-title">
423
  <span class="icon">๐Ÿ•</span> TEMPERAMENT & PERSONALITY
424
  </h2>
 
434
  </span>
435
  </span>
436
  </div>
437
+
438
  <h2 class="section-title">
439
  <span class="icon">๐Ÿ’ช</span> CARE REQUIREMENTS
440
  </h2>
 
485
  <span class="value">{description['Care Level']}</span>
486
  </div>
487
  </div>
488
+
489
  <h2 class="section-title">
490
  <span class="icon">๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ</span> FAMILY COMPATIBILITY
491
  </h2>
 
505
  <span class="value">{description['Good with Children']}</span>
506
  </div>
507
  </div>
508
+
509
  <h2 class="section-title">
510
  <span class="icon">๐Ÿ“</span> DESCRIPTION
511
  </h2>
512
  <div class="description-section">
513
  <p>{description.get('Description', '')}</p>
514
  </div>
515
+
516
  <div class="action-section">
517
  <a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button">
518
  <span class="icon">๐ŸŒ</span>
 
529
  <div class="breed-info">
530
  <div class="model-uncertainty-note">
531
  <span class="icon">โ„น๏ธ</span>
532
+ Note: The model is showing some uncertainty in its predictions.
533
  Here are the most likely breeds based on the available visual features.
534
  </div>
535
  <div class="breeds-list">
536
  '''
537
+
538
  for j, (breed, prob) in enumerate(zip(topk_breeds, relative_probs)):
539
  description = get_dog_description(breed)
540
  dogs_info += f'''
541
+ <div class="breed-option uncertainty-mode">
542
  <div class="breed-header">
543
  <span class="option-number">Option {j+1}</span>
544
  <span class="breed-name">{breed}</span>
 
552
  </div>
553
  '''
554
  dogs_info += '</div></div>'
555
+
556
  dogs_info += '</div>'
557
 
558
 
559
  html_output = f"""
560
+ <div class="dog-info-card">
561
+ {dogs_info}
562
+ </div>
563
+ """
564
+
565
+ initial_state = {
566
+ "dogs_info": dogs_info,
567
+ "image": annotated_image,
568
+ "is_multi_dog": len(dogs) > 1,
569
+ "html_output": html_output
570
+ }
571
+
572
+ return html_output, annotated_image, initial_state
573
+
574
+ except Exception as e:
575
+ error_msg = f"An error occurred: {str(e)}\n\nTraceback:\n{traceback.format_exc()}"
576
+ print(error_msg)
577
+ return error_msg, None, None
578
+
579
+
580
+ def show_details_html(choice, previous_output, initial_state):
581
+ if not choice:
582
+ return previous_output, gr.update(visible=True), initial_state
583
+
584
+ try:
585
+ breed = choice.split("More about ")[-1]
586
+ description = get_dog_description(breed)
587
+ formatted_description = format_description_html(description, breed)
588
+
589
+ html_output = f"""
590
+ <div class="dog-info">
591
+ <h2>{breed}</h2>
592
+ {formatted_description}
593
+ </div>
594
+ """
595
+
596
+ initial_state["current_description"] = html_output
597
+ initial_state["original_buttons"] = initial_state.get("buttons", [])
598
+
599
+ return html_output, gr.update(visible=True), initial_state
600
+ except Exception as e:
601
+ error_msg = f"An error occurred while showing details: {e}"
602
+ print(error_msg)
603
+ return f"<p style='color: red;'>{error_msg}</p>", gr.update(visible=True), initial_state
604
+
605
+
606
+ def format_description_html(description, breed):
607
+ html = "<ul style='list-style-type: none; padding-left: 0;'>"
608
+ if isinstance(description, dict):
609
+ for key, value in description.items():
610
+ if key != "Breed": # ่ทณ้Ž้‡่ค‡็š„ๅ“็จฎ้กฏ็คบ
611
+ if key == "Size":
612
+ html += f'''
613
+ <li style='margin-bottom: 10px;'>
614
+ <span class="tooltip">
615
+ <strong>{key}:</strong>
616
+ <span class="tooltip-icon">โ“˜</span>
617
+ <span class="tooltip-text">
618
+ <strong>Size Categories:</strong><br>
619
+ โ€ข Small: Under 20 pounds<br>
620
+ โ€ข Medium: 20-60 pounds<br>
621
+ โ€ข Large: Over 60 pounds
622
+ </span>
623
+ </span> {value}
624
+ </li>
625
+ '''
626
+ elif key == "Exercise Needs":
627
+ html += f'''
628
+ <li style='margin-bottom: 10px;'>
629
+ <span class="tooltip">
630
+ <strong>{key}:</strong>
631
+ <span class="tooltip-icon">โ“˜</span>
632
+ <span class="tooltip-text">
633
+ <strong>Exercise Needs:</strong><br>
634
+ โ€ข High: 2+ hours of daily exercise<br>
635
+ โ€ข Moderate: 1-2 hours of daily activity<br>
636
+ โ€ข Low: Short walks and play sessions
637
+ </span>
638
+ </span> {value}
639
+ </li>
640
+ '''
641
+ elif key == "Grooming Needs":
642
+ html += f'''
643
+ <li style='margin-bottom: 10px;'>
644
+ <span class="tooltip">
645
+ <strong>{key}:</strong>
646
+ <span class="tooltip-icon">โ“˜</span>
647
+ <span class="tooltip-text">
648
+ <strong>Grooming Requirements:</strong><br>
649
+ โ€ข High: Daily brushing, regular professional care<br>
650
+ โ€ข Moderate: Weekly brushing, occasional grooming<br>
651
+ โ€ข Low: Minimal brushing, basic maintenance
652
+ </span>
653
+ </span> {value}
654
+ </li>
655
+ '''
656
+ elif key == "Care Level":
657
+ html += f'''
658
+ <li style='margin-bottom: 10px;'>
659
+ <span class="tooltip">
660
+ <strong>{key}:</strong>
661
+ <span class="tooltip-icon">โ“˜</span>
662
+ <span class="tooltip-text">
663
+ <strong>Care Level Explained:</strong><br>
664
+ โ€ข High: Needs significant training and attention<br>
665
+ โ€ข Moderate: Regular care and routine needed<br>
666
+ โ€ข Low: More independent, basic care sufficient
667
+ </span>
668
+ </span> {value}
669
+ </li>
670
+ '''
671
+ elif key == "Good with Children":
672
+ html += f'''
673
+ <li style='margin-bottom: 10px;'>
674
+ <span class="tooltip">
675
+ <strong>{key}:</strong>
676
+ <span class="tooltip-icon">โ“˜</span>
677
+ <span class="tooltip-text">
678
+ <strong>Child Compatibility:</strong><br>
679
+ โ€ข Yes: Excellent with kids, patient and gentle<br>
680
+ โ€ข Moderate: Good with older children<br>
681
+ โ€ข No: Better suited for adult households
682
+ </span>
683
+ </span> {value}
684
+ </li>
685
+ '''
686
+ elif key == "Lifespan":
687
+ html += f'''
688
+ <li style='margin-bottom: 10px;'>
689
+ <span class="tooltip">
690
+ <strong>{key}:</strong>
691
+ <span class="tooltip-icon">โ“˜</span>
692
+ <span class="tooltip-text">
693
+ <strong>Average Lifespan:</strong><br>
694
+ โ€ข Short: 6-8 years<br>
695
+ โ€ข Average: 10-15 years<br>
696
+ โ€ข Long: 12-20 years
697
+ </span>
698
+ </span> {value}
699
+ </li>
700
+ '''
701
+ elif key == "Temperament":
702
+ html += f'''
703
+ <li style='margin-bottom: 10px;'>
704
+ <span class="tooltip">
705
+ <strong>{key}:</strong>
706
+ <span class="tooltip-icon">โ“˜</span>
707
+ <span class="tooltip-text">
708
+ <strong>Temperament Guide:</strong><br>
709
+ โ€ข Describes the dog's natural behavior<br>
710
+ โ€ข Important for matching with owner
711
+ </span>
712
+ </span> {value}
713
+ </li>
714
+ '''
715
+ else:
716
+ # ๅ…ถไป–ๆฌ„ไฝไฟๆŒๅŽŸๆจฃ้กฏ็คบ
717
+ html += f"<li style='margin-bottom: 10px;'><strong>{key}:</strong> {value}</li>"
718
+ else:
719
+ html += f"<li>{description}</li>"
720
+ html += "</ul>"
721
+
722
+ # ๆทปๅŠ AKC้€ฃ็ต
723
+ html += f'''
724
+ <div class="action-section">
725
+ <a href="{get_akc_breeds_link(breed)}" target="_blank" class="akc-button">
726
+ <span class="icon">๐ŸŒ</span>
727
+ Learn more about {breed} on AKC website
728
+ </a>
729
+ </div>
730
+ '''
731
+ return html
732
+
733
+
734
+ with gr.Blocks(css="""
735
+ .dog-info-card {
736
+ border: 1px solid #e1e4e8;
737
  margin: 40px 0; /* ๅขžๅŠ ๅก็‰‡้–“่ท */
738
+ padding: 0;
739
+ border-radius: 12px;
740
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
741
  overflow: hidden;
742
  transition: all 0.3s ease;
743
  background: white;
744
+ }
745
+
746
+ .dog-info-card:hover {
747
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
748
+ }
749
+
750
+ .dog-info-header {
751
  padding: 24px 28px; /* ๅขžๅŠ ๅ…ง่ท */
752
  margin: 0;
753
  font-size: 22px;
754
  font-weight: bold;
755
  border-bottom: 1px solid #e1e4e8;
756
+ }
757
+
758
+ .breed-info {
759
  padding: 28px; /* ๅขžๅŠ ๆ•ด้ซ”ๅ…ง่ท */
760
  line-height: 1.6;
761
+ }
762
+
763
+ .section-title {
764
  font-size: 1.3em;
765
  font-weight: 700;
766
  color: #2c3e50;
767
  margin: 32px 0 20px 0;
768
+ padding: 12px 0;
769
  border-bottom: 2px solid #e1e4e8;
770
  text-transform: uppercase;
771
  letter-spacing: 0.5px;
 
773
  align-items: center;
774
  gap: 8px;
775
  position: relative;
776
+ }
777
+
778
+ .icon {
779
  font-size: 1.2em;
780
  display: inline-flex;
781
  align-items: center;
782
  justify-content: center;
783
+ }
784
+
785
+ .info-section, .care-section, .family-section {
786
  display: flex;
787
  flex-wrap: wrap;
788
  gap: 16px;
 
791
  background: #f8f9fa;
792
  border-radius: 12px;
793
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
794
+ }
795
+
796
+ .info-item {
797
  background: white; /* ๆ”น็‚บ็™ฝ่‰ฒ่ƒŒๆ™ฏ */
798
  padding: 14px 18px; /* ๅขžๅŠ ๅ…ง่ท */
799
  border-radius: 8px;
 
804
  border: 1px solid #e1e4e8;
805
  flex: 1 1 auto;
806
  min-width: 200px;
807
+ }
808
+
809
+ .label {
810
  color: #666;
811
  font-weight: 600;
812
  font-size: 1.1rem;
813
+ }
814
+
815
+ .value {
816
  color: #2c3e50;
817
  font-weight: 500;
818
  font-size: 1.1rem;
819
+ }
820
+
821
+ .temperament-section {
822
  background: #f8f9fa;
823
  padding: 20px; /* ๅขžๅŠ ๅ…ง่ท */
824
  border-radius: 12px;
825
  margin-bottom: 28px; /* ๅขžๅŠ ้–“่ท */
826
  color: #444;
827
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
828
+ }
829
+
830
+ .description-section {
831
  background: #f8f9fa;
832
  padding: 24px; /* ๅขžๅŠ ๅ…ง่ท */
833
  border-radius: 12px;
 
836
  color: #444;
837
  border: 1px solid #e1e4e8; /* ๆทปๅŠ ้‚Šๆก† */
838
  fontsize: 1.1rem;
839
+ }
840
+ .description-section p {
 
841
  margin: 0;
842
  padding: 0;
843
  text-align: justify; /* ๆ–‡ๅญ—ๅ…ฉ็ซฏๅฐ้ฝŠ */
844
  word-wrap: break-word; /* ็ขบไฟ้•ทๅ–ฎๅญ—ๆœƒๆ›่กŒ */
845
  white-space: pre-line; /* ไฟ็•™ๆ›่กŒไฝ†ๅˆไฝต็ฉบ็™ฝ */
846
  max-width: 100%; /* ็ขบไฟไธๆœƒ่ถ…ๅ‡บๅฎนๅ™จ */
847
+ }
848
+
849
+ .action-section {
850
  margin-top: 24px;
851
  text-align: center;
852
+ }
853
+
854
  .akc-button,
855
  .breed-section .akc-link,
856
+ .breed-option .akc-link {
857
  display: inline-flex;
858
  align-items: center;
859
+ padding: 14px 28px;
860
  background: linear-gradient(145deg, #00509E, #003F7F);
861
  color: white;
862
  border-radius: 12px; /* ๅขžๅŠ ๅœ“่ง’ */
 
865
  transition: all 0.3s ease;
866
  font-weight: 600;
867
  font-size: 1.1em;
868
+ box-shadow:
869
  0 2px 4px rgba(0,0,0,0.1),
870
  inset 0 1px 1px rgba(255,255,255,0.1);
871
+ border: 1px solid rgba(255,255,255,0.1);
872
+ }
873
+
874
  .akc-button:hover,
875
  .breed-section .akc-link:hover,
876
+ .breed-option .akc-link:hover {
877
  background: linear-gradient(145deg, #003F7F, #00509E);
878
+ transform: translateY(-2px);
879
  color: white;
880
+ box-shadow:
881
+ 0 6px 12px rgba(0,0,0,0.2),
882
  inset 0 1px 1px rgba(255,255,255,0.2);
883
+ border: 1px solid rgba(255,255,255,0.2);
884
+ }
885
+ .icon {
886
+ font-size: 1.3em;
887
+ filter: drop-shadow(0 1px 1px rgba(0,0,0,0.2));
888
+ }
889
+
890
+ .warning-message {
 
891
  display: flex;
892
  align-items: center;
893
  gap: 8px;
 
897
  padding: 16px;
898
  background: #fff5f5;
899
  border-radius: 8px;
900
+ }
901
+
902
+ .model-uncertainty-note {
903
  display: flex;
904
  align-items: center;
905
  gap: 12px;
 
909
  margin-bottom: 20px;
910
  color: #495057;
911
  border-radius: 4px;
912
+ }
913
+
914
+ .breeds-list {
915
  display: flex;
916
  flex-direction: column;
917
  gap: 20px;
918
+ }
919
+
920
+ .breed-option {
921
  background: white;
922
  border: 1px solid #e1e4e8;
923
  border-radius: 8px;
924
  overflow: hidden;
925
+ }
926
+
927
+ .breed-header {
928
  display: flex;
929
  align-items: center;
930
  padding: 16px;
931
  background: #f8f9fa;
932
  gap: 12px;
933
  border-bottom: 1px solid #e1e4e8;
934
+ }
935
+
936
+ .option-number {
937
  font-weight: 600;
938
  color: #666;
939
  padding: 4px 8px;
940
  background: #e1e4e8;
941
  border-radius: 4px;
942
+ }
943
+
944
+ .breed-name {
945
  font-size: 1.5em;
946
  font-weight: bold;
947
  color: #2c3e50;
948
  flex-grow: 1;
949
+ }
950
+
951
+ .confidence-badge {
952
  padding: 4px 12px;
953
  border-radius: 20px;
954
  font-size: 0.9em;
955
  font-weight: 500;
956
+ }
957
+
958
+ .breed-content {
959
  padding: 20px;
960
+ }
961
+ .breed-content li {
 
962
  margin-bottom: 8px;
963
  display: flex;
964
  align-items: flex-start; /* ๆ”น็‚บ้ ‚้ƒจๅฐ้ฝŠ */
965
  gap: 8px;
966
  flex-wrap: wrap; /* ๅ…่จฑๅ…งๅฎนๆ›่กŒ */
967
+ }
968
+ .breed-content li strong {
 
969
  flex: 0 0 auto; /* ไธ่ฎ“ๆจ™้กŒ็ธฎๆ”พ */
970
  min-width: 100px; /* ็ตฆๆจ™้กŒไธ€ๅ€‹ๅ›บๅฎšๆœ€ๅฐๅฏฌๅบฆ */
971
+ }
972
+
973
+ ul {
974
  padding-left: 0;
975
  margin: 0;
976
  list-style-type: none;
977
+ }
978
+
979
+ li {
980
  margin-bottom: 8px;
981
  display: flex;
982
  align-items: center;
983
  gap: 8px;
984
+ }
985
+ .akc-link {
 
986
  color: white;
987
  text-decoration: none;
988
  font-weight: 600;
989
  font-size: 1.1em;
990
  transition: all 0.3s ease;
991
+ }
992
+
993
+ .akc-link:hover {
994
  text-decoration: underline;
995
  color: #D3E3F0;
996
+ }
997
+ .tooltip {
998
  position: relative;
999
  display: inline-flex;
1000
  align-items: center;
1001
  gap: 4px;
1002
  cursor: help;
1003
+ }
1004
+ .tooltip .tooltip-icon {
 
1005
  font-size: 14px;
1006
  color: #666;
1007
+ }
1008
+ .tooltip .tooltip-text {
 
1009
  visibility: hidden;
1010
  width: 250px;
1011
  background-color: rgba(44, 62, 80, 0.95);
 
1024
  line-height: 1.3;
1025
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
1026
  border: 1px solid rgba(255, 255, 255, 0.1)
1027
+ margin-bottom: 10px;
1028
+ }
1029
+ .tooltip.tooltip-left .tooltip-text {
1030
+ left: 0;
 
1031
  transform: translateX(0);
1032
+ }
1033
+ .tooltip.tooltip-right .tooltip-text {
 
1034
  left: auto;
1035
  right: 0;
1036
  transform: translateX(0);
1037
+ }
1038
+ .tooltip-text strong {
1039
+ color: white !important;
1040
+ background-color: transparent !important;
 
1041
  display: block; /* ่ฎ“ๆจ™้กŒ็จ็ซ‹ไธ€่กŒ */
1042
  margin-bottom: 2px; /* ๅขžๅŠ ๆจ™้กŒไธ‹ๆ–น้–“่ท */
1043
  padding-bottom: 2px; /* ๅŠ ๅ…ฅๅฐ้–“่ท */
1044
  border-bottom: 1px solid rgba(255,255,255,0.2);
1045
+ }
1046
+ .tooltip-text {
 
1047
  font-size: 13px; /* ็จๅพฎ็ธฎๅฐๅญ—้ซ” */
1048
+ }
1049
+
1050
  /* ่ชฟๆ•ดๅˆ—่กจ็ฌฆ่™Ÿๅ’Œๆ–‡ๅญ—็š„้–“่ท */
1051
+ .tooltip-text ul {
1052
  margin: 0;
1053
  padding-left: 15px; /* ๆธ›ๅฐ‘ๅˆ—่กจ็ฌฆ่™Ÿ็š„็ธฎ้€ฒ */
1054
+ }
1055
+
1056
+ .tooltip-text li {
1057
  margin-bottom: 1px; /* ๆธ›ๅฐ‘ๅˆ—่กจ้ …็›ฎ้–“็š„้–“่ท */
1058
+ }
1059
+ .tooltip-text br {
1060
  line-height: 1.2; /* ๆธ›ๅฐ‘่กŒ่ท */
1061
+ }
1062
+
1063
+ .tooltip .tooltip-text::after {
1064
  content: "";
1065
  position: absolute;
1066
  top: 100%;
 
1069
  border-width: 5px;
1070
  border-style: solid;
1071
  border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
1072
+ }
1073
+ .tooltip-left .tooltip-text::after {
 
1074
  left: 20%;
1075
+ }
1076
+
1077
  /* ๅณๅด็ฎญ้ ญ */
1078
+ .tooltip-right .tooltip-text::after {
1079
  left: 80%;
1080
+ }
1081
+ .tooltip:hover .tooltip-text {
 
1082
  visibility: visible;
1083
  opacity: 1;
1084
+ }
1085
+ .tooltip .tooltip-text::after {
 
1086
  content: "";
1087
  position: absolute;
1088
  top: 100%;
 
1091
  border-width: 8px;
1092
  border-style: solid;
1093
  border-color: rgba(44, 62, 80, 0.95) transparent transparent transparent;
1094
+ }
1095
+ .uncertainty-mode .tooltip .tooltip-text {
 
1096
  position: absolute;
1097
  left: 100%;
1098
  bottom: auto;
 
1100
  transform: translateY(-50%);
1101
  margin-left: 10px;
1102
  z-index: 1000; /* ็ขบไฟๆ็คบๆก†ๅœจๆœ€ไธŠๅฑค */
1103
+ }
1104
+
1105
+ .uncertainty-mode .tooltip .tooltip-text::after {
1106
  content: "";
1107
  position: absolute;
1108
  top: 50%;
 
1111
  border-width: 5px;
1112
  border-style: solid;
1113
  border-color: transparent rgba(44, 62, 80, 0.95) transparent transparent;
1114
+ }
1115
+ .uncertainty-mode .breed-content {
 
1116
  font-size: 1.1rem; /* ๅขžๅŠ ๅญ—้ซ”ๅคงๅฐ */
1117
+ }
 
1118
  .description-section,
1119
  .description-section p,
1120
  .temperament-section,
1121
  .temperament-section .value,
1122
  .info-item,
1123
  .info-item .value,
1124
+ .breed-content {
1125
  font-size: 1.1rem !important; /* ไฝฟ็”จ !important ็ขบไฟ่ฆ†่“‹ๅ…ถไป–ๆจฃๅผ */
1126
+ }
 
 
 
1127
 
1128
+ .recommendation-card {
1129
+ margin-bottom: 40px;
 
 
 
1130
  }
 
 
1131
 
1132
+ .compatibility-scores {
1133
+ background: #f8f9fa;
1134
+ padding: 24px;
1135
+ border-radius: 12px;
1136
+ margin: 20px 0;
1137
+ }
1138
 
1139
+ .score-item {
1140
+ margin: 15px 0;
1141
+ }
1142
 
1143
+ .progress-bar {
1144
+ height: 12px;
1145
+ background-color: #e9ecef;
1146
+ border-radius: 6px;
1147
+ overflow: hidden;
1148
+ margin: 8px 0;
1149
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1150
 
1151
+ .progress {
1152
+ height: 100%;
1153
+ background: linear-gradient(90deg, #34C759, #30B350);
1154
+ border-radius: 6px;
1155
+ transition: width 0.6s ease;
1156
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1157
 
1158
+ .percentage {
1159
+ float: right;
1160
+ color: #34C759;
1161
+ font-weight: 600;
1162
+ }
1163
 
1164
+ .breed-details-section {
1165
+ margin: 30px 0;
1166
+ }
1167
+
1168
+ .subsection-title {
1169
+ font-size: 1.2em;
1170
+ color: #2c3e50;
1171
+ margin-bottom: 20px;
1172
+ display: flex;
1173
+ align-items: center;
1174
+ gap: 8px;
1175
+ }
1176
+
1177
+ .details-grid {
1178
+ display: grid;
1179
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1180
+ gap: 20px;
1181
+ background: #f8f9fa;
1182
+ padding: 20px;
1183
+ border-radius: 12px;
1184
+ border: 1px solid #e1e4e8;
1185
+ }
1186
+
1187
+ .detail-item {
1188
+ background: white;
1189
+ padding: 15px;
1190
+ border-radius: 8px;
1191
+ border: 1px solid #e1e4e8;
1192
+ }
1193
+
1194
+ .description-text {
1195
+ line-height: 1.8;
1196
+ color: #444;
1197
+ margin: 0;
1198
+ padding: 24px 30px; /* ่ชฟๆ•ดๅ…ง้ƒจ้–“่ท๏ผŒๅพž 20px ๆ”น็‚บ 24px 30px */
1199
+ background: #f8f9fa;
1200
+ border-radius: 12px;
1201
+ border: 1px solid #e1e4e8;
1202
+ text-align: justify; /* ๆทปๅŠ ๆ–‡ๅญ—ๅฐ้ฝŠ */
1203
+ word-wrap: break-word; /* ็ขบไฟ้•ทๆ–‡ๅญ—ๆœƒๆ›่กŒ */
1204
+ word-spacing: 1px; /* ๅŠ ๅ…ฅๅญ—้–“่ท */
1205
+ }
1206
+
1207
+ /* ๅทฅๅ…ทๆ็คบๆ”น้€ฒ */
1208
+ .tooltip {
1209
+ position: relative;
1210
+ display: inline-flex;
1211
+ align-items: center;
1212
+ gap: 4px;
1213
+ cursor: help;
1214
+ padding: 5px 0;
1215
+ }
1216
+
1217
+ .tooltip .tooltip-text {
1218
+ visibility: hidden;
1219
+ width: 280px;
1220
+ background-color: rgba(44, 62, 80, 0.95);
1221
+ color: white;
1222
+ text-align: left;
1223
+ border-radius: 8px;
1224
+ padding: 12px 15px;
1225
+ position: absolute;
1226
+ z-index: 1000;
1227
+ bottom: calc(100% + 15px);
1228
+ left: 50%;
1229
+ transform: translateX(-50%);
1230
+ opacity: 0;
1231
+ transition: all 0.3s ease;
1232
+ font-size: 14px;
1233
+ line-height: 1.4;
1234
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
1235
+ white-space: normal;
1236
+ }
1237
+
1238
+ .tooltip:hover .tooltip-text {
1239
+ visibility: visible;
1240
+ opacity: 1;
1241
+ }
1242
+
1243
+ .score-badge {
1244
+ background-color: #34C759;
1245
+ color: white;
1246
+ padding: 6px 12px;
1247
+ border-radius: 20px;
1248
+ font-size: 0.9em;
1249
+ margin-left: 10px;
1250
+ font-weight: 500;
1251
+ box-shadow: 0 2px 4px rgba(52, 199, 89, 0.2);
1252
+ }
1253
+ """) as iface:
1254
 
1255
+ gr.HTML("<h1 style='text-align: center;'>๐Ÿถ Dog Breed Classifier ๐Ÿ”</h1>")
1256
+
1257
+ # ไฝฟ็”จ Tabs ไพ†ๅˆ†้š”ๅ…ฉๅ€‹ๅŠŸ่ƒฝ
1258
+ with gr.Tabs():
1259
+ # ็ฌฌไธ€ๅ€‹ Tab๏ผšๅŽŸๆœ‰็š„่พจ่ญ˜ๅŠŸ่ƒฝ
1260
+ with gr.TabItem("Breed Detection"):
1261
+ gr.HTML("<p style='text-align: center;'>Upload a picture of a dog, and the model will predict its breed and provide detailed information!</p>")
1262
+ gr.HTML("<p style='text-align: center; color: #666; font-size: 0.9em;'>Note: The model's predictions may not always be 100% accurate, and it is recommended to use the results as a reference.</p>")
1263
+
1264
+ with gr.Row():
1265
+ input_image = gr.Image(label="Upload a dog image", type="pil")
1266
+ output_image = gr.Image(label="Annotated Image")
1267
+
1268
+ output = gr.HTML(label="Prediction Results")
1269
+ initial_state = gr.State()
1270
+
1271
+ input_image.change(
1272
+ predict,
1273
+ inputs=input_image,
1274
+ outputs=[output, output_image, initial_state]
1275
+ )
1276
+
1277
+ gr.Examples(
1278
+ examples=['Border_Collie.jpg',
1279
+ 'Golden_Retriever.jpeg',
1280
+ 'Saint_Bernard.jpeg',
1281
+ 'Samoyed.jpg',
1282
+ 'French_Bulldog.jpeg'],
1283
+ inputs=input_image
1284
+ )
1285
+
1286
+ # ็ฌฌไบŒๅ€‹ Tab๏ผšๅ“็จฎๆฏ”่ผƒๅŠŸ่ƒฝ
1287
+ with gr.TabItem("Breed Comparison"):
1288
+ gr.HTML("<p style='text-align: center;'>Select two dog breeds to compare their characteristics and care requirements.</p>")
1289
+
1290
+ with gr.Row():
1291
+ breed1_dropdown = gr.Dropdown(
1292
+ choices=dog_breeds,
1293
+ label="Select First Breed",
1294
+ value="Golden_Retriever"
1295
+ )
1296
+ breed2_dropdown = gr.Dropdown(
1297
+ choices=dog_breeds,
1298
+ label="Select Second Breed",
1299
+ value="Border_Collie"
1300
+ )
1301
+
1302
+ compare_btn = gr.Button("Compare Breeds")
1303
+ comparison_output = gr.HTML(label="Comparison Results")
1304
+
1305
+ def show_comparison(breed1, breed2):
1306
+ if not breed1 or not breed2:
1307
+ return "Please select two breeds to compare"
1308
+
1309
+ breed1_info = get_dog_description(breed1)
1310
+ breed2_info = get_dog_description(breed2)
1311
+
1312
+ html_output = f"""
1313
+ <div class="dog-info-card">
1314
+ <div class="comparison-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
1315
+ <div class="breed-info">
1316
+ <h2 class="section-title">
1317
+ <span class="icon">๐Ÿ•</span> {breed1.replace('_', ' ')}
1318
+ </h2>
1319
+ <div class="info-section">
1320
+ <div class="info-item">
1321
+ <span class="tooltip">
1322
+ <span class="icon">๐Ÿ“</span>
1323
+ <span class="label">Size:</span>
1324
+ <span class="value">{breed1_info['Size']}</span>
1325
+ </span>
1326
+ </div>
1327
+ <div class="info-item">
1328
+ <span class="tooltip">
1329
+ <span class="icon">๐Ÿƒ</span>
1330
+ <span class="label">Exercise Needs:</span>
1331
+ <span class="value">{breed1_info['Exercise Needs']}</span>
1332
+ </span>
1333
+ </div>
1334
+ <div class="info-item">
1335
+ <span class="tooltip">
1336
+ <span class="icon">โœ‚๏ธ</span>
1337
+ <span class="label">Grooming:</span>
1338
+ <span class="value">{breed1_info['Grooming Needs']}</span>
1339
+ </span>
1340
+ </div>
1341
+ <div class="info-item">
1342
+ <span class="tooltip">
1343
+ <span class="icon">๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ</span>
1344
+ <span class="label">Good with Children:</span>
1345
+ <span class="value">{breed1_info['Good with Children']}</span>
1346
+ </span>
1347
+ </div>
1348
+ </div>
1349
+ </div>
1350
+
1351
+ <div class="breed-info">
1352
+ <h2 class="section-title">
1353
+ <span class="icon">๐Ÿ•</span> {breed2.replace('_', ' ')}
1354
+ </h2>
1355
+ <div class="info-section">
1356
+ <div class="info-item">
1357
+ <span class="tooltip">
1358
+ <span class="icon">๐Ÿ“</span>
1359
+ <span class="label">Size:</span>
1360
+ <span class="value">{breed2_info['Size']}</span>
1361
+ </span>
1362
+ </div>
1363
+ <div class="info-item">
1364
+ <span class="tooltip">
1365
+ <span class="icon">๐Ÿƒ</span>
1366
+ <span class="label">Exercise Needs:</span>
1367
+ <span class="value">{breed2_info['Exercise Needs']}</span>
1368
+ </span>
1369
+ </div>
1370
+ <div class="info-item">
1371
+ <span class="tooltip">
1372
+ <span class="icon">โœ‚๏ธ</span>
1373
+ <span class="label">Grooming:</span>
1374
+ <span class="value">{breed2_info['Grooming Needs']}</span>
1375
+ </span>
1376
+ </div>
1377
+ <div class="info-item">
1378
+ <span class="tooltip">
1379
+ <span class="icon">๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ</span>
1380
+ <span class="label">Good with Children:</span>
1381
+ <span class="value">{breed2_info['Good with Children']}</span>
1382
+ </span>
1383
+ </div>
1384
+ </div>
1385
+ </div>
1386
+ </div>
1387
+ </div>
1388
+ """
1389
+ return html_output
1390
+
1391
+ compare_btn.click(
1392
+ show_comparison,
1393
+ inputs=[breed1_dropdown, breed2_dropdown],
1394
+ outputs=comparison_output
1395
+ )
1396
+
1397
+ # ็ฌฌไธ‰ๅ€‹ Tab๏ผšๅ“็จฎๆŽจ่–ฆๅŠŸ่ƒฝ
1398
+ with gr.TabItem("Breed Recommendation"):
1399
+ gr.HTML("<p style='text-align: center;'>Tell us about your lifestyle, and we'll recommend the perfect dog breeds for you!</p>")
1400
+
1401
+ with gr.Row():
1402
+ with gr.Column():
1403
+ living_space = gr.Radio(
1404
+ choices=["apartment", "house_small", "house_large"],
1405
+ label="What type of living space do you have?",
1406
+ info="Choose your current living situation",
1407
+ value="apartment"
1408
+ )
1409
+
1410
+ exercise_time = gr.Slider(
1411
+ minimum=0,
1412
+ maximum=180,
1413
+ value=60,
1414
+ label="Daily exercise time (minutes)",
1415
+ info="Consider walks, play time, and training"
1416
+ )
1417
+
1418
+ grooming_commitment = gr.Radio(
1419
+ choices=["low", "medium", "high"],
1420
+ label="Grooming commitment level",
1421
+ info="Low: monthly, Medium: weekly, High: daily",
1422
+ value="medium"
1423
+ )
1424
+
1425
+ with gr.Column():
1426
+ experience_level = gr.Radio(
1427
+ choices=["beginner", "intermediate", "advanced"],
1428
+ label="Dog ownership experience",
1429
+ info="Be honest - this helps find the right match",
1430
+ value="beginner"
1431
+ )
1432
+
1433
+ has_children = gr.Checkbox(
1434
+ label="Have children at home",
1435
+ info="Helps recommend child-friendly breeds"
1436
+ )
1437
+
1438
+ noise_tolerance = gr.Radio(
1439
+ choices=["low", "medium", "high"],
1440
+ label="Noise tolerance level",
1441
+ info="Some breeds are more vocal than others",
1442
+ value="medium"
1443
+ )
1444
+
1445
+
1446
+ # ่จญ็ฝฎๆŒ‰้ˆ•็š„้ปžๆ“Šไบ‹ไปถ
1447
+ get_recommendations_btn = gr.Button("Find My Perfect Match! ๐Ÿ”", variant="primary")
1448
+ recommendation_output = gr.HTML(label="Breed Recommendations")
1449
+
1450
+ def process_recommendations(living_space, exercise_time, grooming_commitment,
1451
+ experience_level, has_children, noise_tolerance):
1452
+ try:
1453
+ user_prefs = UserPreferences(
1454
+ living_space=living_space,
1455
+ exercise_time=exercise_time,
1456
+ grooming_commitment=grooming_commitment,
1457
+ experience_level=experience_level,
1458
+ has_children=has_children,
1459
+ noise_tolerance=noise_tolerance,
1460
+ space_for_play=True if living_space != "apartment" else False,
1461
+ other_pets=False,
1462
+ climate="moderate"
1463
+ )
1464
+
1465
+ recommendations = get_breed_recommendations(user_prefs)
1466
+ return format_recommendation_html(recommendations)
1467
+ except Exception as e:
1468
+ print(f"Error in process_recommendations: {str(e)}")
1469
+ return f"An error occurred: {str(e)}"
1470
+
1471
+ # ้€™่กŒๆ˜ฏ้—œ้ต - ็ขบไฟๆŒ‰้ˆ•้ปžๆ“Šไบ‹ไปถๆœ‰ๆญฃ็ขบ้€ฃๆŽฅๅˆฐ่™•็†ๅ‡ฝๆ•ธ
1472
+ get_recommendations_btn.click(
1473
+ fn=process_recommendations, # ่™•็†ๅ‡ฝๆ•ธ
1474
+ inputs=[
1475
+ living_space,
1476
+ exercise_time,
1477
+ grooming_commitment,
1478
+ experience_level,
1479
+ has_children,
1480
+ noise_tolerance
1481
+ ],
1482
+ outputs=recommendation_output # ่ผธๅ‡บ็ตๆžœ็š„ไฝ็ฝฎ
1483
+ )
1484
+
1485
+ gr.HTML('For more details on this project and other work, feel free to visit my GitHub <a href="https://github.com/Eric-Chung-0511/Learning-Record/tree/main/Data%20Science%20Projects/Dog_Breed_Classifier">Dog Breed Classifier</a>')
1486
 
1487
  if __name__ == "__main__":
1488
+
1489
+ iface.launch(share=True)