nodda commited on
Commit
b123157
·
1 Parent(s): aa26bce

feat: 식물종 선택 드롭다운 + 지원 작물 안내 배너 + plant_filter 진단 개선

Browse files
Files changed (1) hide show
  1. app.py +74 -21
app.py CHANGED
@@ -321,38 +321,46 @@ TTA_TF = [
321
  T.Compose([T.Resize(256), T.CenterCrop(224), T.RandomHorizontalFlip(p=1.0), T.ToTensor(), _NORM]),
322
  ]
323
 
324
- def _predict_ensemble(pil_img):
325
- """TTA + 앙상블 예측"""
326
  if not _models:
327
  return None, 0.0
328
-
329
  device = next(_models[0][0].parameters()).device
330
-
331
  # TTA 배치 준비
332
  tensors = [tf(pil_img) for tf in TTA_TF]
333
  batch = torch.stack(tensors).to(device)
334
-
335
  logits_sum = torch.zeros(NUM_CLASSES, device=device)
336
-
337
  with torch.no_grad():
338
  for model, weight, desc in _models:
339
  outs = model(batch) # [TTA, 38]
340
  avg = outs.mean(0) # [38]
341
  logits_sum += avg * weight
342
-
343
  # 칼리브레이션
344
  for idx, factor in CALIBRATION.items():
345
  logits_sum[idx] = logits_sum[idx] * factor
346
-
 
 
 
 
 
 
 
 
347
  probs = torch.softmax(logits_sum, dim=0)
348
  top3_probs, top3_ids = probs.topk(3)
349
-
350
  top3 = [(ID2LABEL[int(i)], float(p)) for i, p in zip(top3_ids, top3_probs)]
351
  return top3, float(probs.max())
352
 
353
  # ── Main diagnosis function ──────────────────────────────────────────────────
354
  @spaces.GPU(duration=30)
355
- def diagnose(image, language):
356
  t0 = time.time()
357
 
358
  if image is None:
@@ -372,9 +380,24 @@ def diagnose(image, language):
372
  if not isinstance(image, Image.Image):
373
  image = Image.fromarray(image)
374
  pil_img = image.convert("RGB")
375
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  # 예측
377
- top3, conf = _predict_ensemble(pil_img)
378
  elapsed = time.time() - t0
379
 
380
  if top3 is None:
@@ -493,14 +516,50 @@ with gr.Blocks(
493
  </div>
494
  """)
495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  with gr.Row():
497
  with gr.Column(scale=1):
 
 
 
 
 
 
498
  img_input = gr.Image(
499
  type="pil",
500
  label="📸 Plant Photo / 식물 사진",
501
  elem_classes="upload-area",
502
  sources=["upload", "webcam", "clipboard"],
503
- height=320,
504
  )
505
  lang_select = gr.Radio(
506
  choices=["English", "한국어 (Korean)"],
@@ -512,12 +571,6 @@ with gr.Blocks(
512
  variant="primary",
513
  size="lg",
514
  )
515
-
516
- gr.Markdown("""
517
- **Supported crops**: Apple, Blueberry, Cherry, Corn, Grape, Orange, Peach, Pepper, Potato, Raspberry, Soybean, Squash, Strawberry, Tomato
518
-
519
- **38 disease/healthy classes** | PlantVillage accuracy: **99.91%**
520
- """)
521
 
522
  with gr.Column(scale=1):
523
  label_output = gr.Label(
@@ -532,12 +585,12 @@ with gr.Blocks(
532
 
533
  btn.click(
534
  fn=diagnose,
535
- inputs=[img_input, lang_select],
536
  outputs=[label_output, detail_output],
537
  )
538
  img_input.change(
539
  fn=diagnose,
540
- inputs=[img_input, lang_select],
541
  outputs=[label_output, detail_output],
542
  )
543
 
 
321
  T.Compose([T.Resize(256), T.CenterCrop(224), T.RandomHorizontalFlip(p=1.0), T.ToTensor(), _NORM]),
322
  ]
323
 
324
+ def _predict_ensemble(pil_img, plant_filter=None):
325
+ """TTA + 앙상블 예측. plant_filter가 있으면 해당 식물 클래스만 후보로 사용."""
326
  if not _models:
327
  return None, 0.0
328
+
329
  device = next(_models[0][0].parameters()).device
330
+
331
  # TTA 배치 준비
332
  tensors = [tf(pil_img) for tf in TTA_TF]
333
  batch = torch.stack(tensors).to(device)
334
+
335
  logits_sum = torch.zeros(NUM_CLASSES, device=device)
336
+
337
  with torch.no_grad():
338
  for model, weight, desc in _models:
339
  outs = model(batch) # [TTA, 38]
340
  avg = outs.mean(0) # [38]
341
  logits_sum += avg * weight
342
+
343
  # 칼리브레이션
344
  for idx, factor in CALIBRATION.items():
345
  logits_sum[idx] = logits_sum[idx] * factor
346
+
347
+ # 식물종 필터 — 선택한 작물 클래스만 활성화
348
+ if plant_filter:
349
+ mask = torch.full((NUM_CLASSES,), float('-inf'), device=device)
350
+ for idx, label in ID2LABEL.items():
351
+ if plant_filter.lower() in label.lower():
352
+ mask[idx] = 0.0
353
+ logits_sum = logits_sum + mask
354
+
355
  probs = torch.softmax(logits_sum, dim=0)
356
  top3_probs, top3_ids = probs.topk(3)
357
+
358
  top3 = [(ID2LABEL[int(i)], float(p)) for i, p in zip(top3_ids, top3_probs)]
359
  return top3, float(probs.max())
360
 
361
  # ── Main diagnosis function ──────────────────────────────────────────────────
362
  @spaces.GPU(duration=30)
363
+ def diagnose(image, language, plant_type="Auto-detect"):
364
  t0 = time.time()
365
 
366
  if image is None:
 
380
  if not isinstance(image, Image.Image):
381
  image = Image.fromarray(image)
382
  pil_img = image.convert("RGB")
383
+
384
+ # 식물종 필터 매핑
385
+ PLANT_KEYWORD_MAP = {
386
+ "Apple": "Apple", "Blueberry": "Blueberry", "Cherry": "Cherry",
387
+ "Corn": "Corn", "Maize": "Corn", "Grape": "Grape", "Orange": "Orange",
388
+ "Peach": "Peach", "Pepper": "Pepper", "Potato": "Potato",
389
+ "Raspberry": "Raspberry", "Soybean": "Soybean", "Squash": "Squash",
390
+ "Strawberry": "Strawberry", "Tomato": "Tomato",
391
+ }
392
+ selected_plant = None
393
+ if plant_type and "Auto-detect" not in plant_type:
394
+ for kw, plant in PLANT_KEYWORD_MAP.items():
395
+ if kw.lower() in plant_type.lower():
396
+ selected_plant = plant
397
+ break
398
+
399
  # 예측
400
+ top3, conf = _predict_ensemble(pil_img, plant_filter=selected_plant)
401
  elapsed = time.time() - t0
402
 
403
  if top3 is None:
 
516
  </div>
517
  """)
518
 
519
+ # 지원 작물 목록
520
+ SUPPORTED_PLANTS = [
521
+ "🔍 Auto-detect (자동감지)",
522
+ "🍎 Apple (사과)",
523
+ "🫐 Blueberry (블루베리)",
524
+ "🍒 Cherry (체리)",
525
+ "🌽 Corn / Maize (옥수수)",
526
+ "🍇 Grape (포도)",
527
+ "🍊 Orange (오렌지)",
528
+ "🍑 Peach (복숭아)",
529
+ "🫑 Bell Pepper (피망/고추)",
530
+ "🥔 Potato (감자)",
531
+ "🫐 Raspberry (라즈베리)",
532
+ "🌱 Soybean (콩)",
533
+ "🎃 Squash (호박)",
534
+ "🍓 Strawberry (딸기)",
535
+ "🍅 Tomato (토마토)",
536
+ ]
537
+
538
+ gr.HTML("""
539
+ <div style="background:#fff8e1;border-left:4px solid #ffc107;padding:12px 16px;border-radius:8px;margin-bottom:12px;">
540
+ <b>⚠️ 지원 작물 안내 / Supported Crops</b><br>
541
+ 이 앱은 아래 <b>14종 작물</b>의 병해를 진단합니다. 지원하지 않는 식물은 오진될 수 있습니다.<br>
542
+ <small>This app diagnoses diseases for <b>14 specific crops</b> only. Other plants may be misclassified.</small><br>
543
+ <br>
544
+ 🍎 Apple &nbsp; 🫐 Blueberry &nbsp; 🍒 Cherry &nbsp; 🌽 Corn &nbsp; 🍇 Grape &nbsp; 🍊 Orange &nbsp; 🍑 Peach<br>
545
+ 🫑 Pepper &nbsp; 🥔 Potato &nbsp; 🫐 Raspberry &nbsp; 🌱 Soybean &nbsp; 🎃 Squash &nbsp; 🍓 Strawberry &nbsp; 🍅 Tomato
546
+ </div>
547
+ """)
548
+
549
  with gr.Row():
550
  with gr.Column(scale=1):
551
+ plant_select = gr.Dropdown(
552
+ choices=SUPPORTED_PLANTS,
553
+ value="🔍 Auto-detect (자동감지)",
554
+ label="🌿 Select Crop / 작물 선택 (선택 시 정확도 향상)",
555
+ info="작물을 먼저 선택하면 더 정확하게 진단합니다",
556
+ )
557
  img_input = gr.Image(
558
  type="pil",
559
  label="📸 Plant Photo / 식물 사진",
560
  elem_classes="upload-area",
561
  sources=["upload", "webcam", "clipboard"],
562
+ height=300,
563
  )
564
  lang_select = gr.Radio(
565
  choices=["English", "한국어 (Korean)"],
 
571
  variant="primary",
572
  size="lg",
573
  )
 
 
 
 
 
 
574
 
575
  with gr.Column(scale=1):
576
  label_output = gr.Label(
 
585
 
586
  btn.click(
587
  fn=diagnose,
588
+ inputs=[img_input, lang_select, plant_select],
589
  outputs=[label_output, detail_output],
590
  )
591
  img_input.change(
592
  fn=diagnose,
593
+ inputs=[img_input, lang_select, plant_select],
594
  outputs=[label_output, detail_output],
595
  )
596