Pro-Coder commited on
Commit
1c964a9
·
verified ·
1 Parent(s): 363722f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -79
app.py CHANGED
@@ -23,23 +23,6 @@ import spaces
23
  import requests
24
  from PIL import Image
25
  from transformers import pipeline
26
- from pathlib import Path
27
-
28
- def data_badge(content):
29
- """Render a compact informational badge for data/model disclosures."""
30
- return f"""
31
- <div style="
32
- padding: 12px 16px;
33
- margin: 8px 0 16px 0;
34
- border-radius: 10px;
35
- border: 1px solid rgba(120, 130, 150, 0.25);
36
- background: rgba(120, 130, 150, 0.08);
37
- font-size: 0.9em;
38
- line-height: 1.5;
39
- ">
40
- {content}
41
- </div>
42
- """
43
 
44
  # ---------------------------------------------------------------------------
45
  # Config — override any of these via Space "Variables and secrets"
@@ -54,7 +37,9 @@ HF_TOKEN = os.environ.get("HF_TOKEN") # add as a Space SECRET to enable the cha
54
  ROUTER_URL = "https://router.huggingface.co/v1/chat/completions"
55
 
56
  PAGE_DISCLAIMER = (
57
- "This tool is a research/portfolio project, not a medical device."
 
 
58
  )
59
 
60
  # Optional second classifier specifically for pigmented-lesion cases (HAM10000-based,
@@ -191,7 +176,15 @@ def chat_completion(messages, max_tokens=500):
191
  json={"model": CHAT_MODEL_ID, "messages": messages, "max_tokens": max_tokens},
192
  timeout=60,
193
  )
194
- resp.raise_for_status()
 
 
 
 
 
 
 
 
195
  data = resp.json()
196
  return data["choices"][0]["message"]["content"]
197
 
@@ -380,35 +373,59 @@ def chat_respond(message, history):
380
  # ---------------------------------------------------------------------------
381
  # Model Performance tab data loading
382
  # ---------------------------------------------------------------------------
383
- BASE_DIR = Path(__file__).resolve().parent
384
- ASSETS_DIR = BASE_DIR / "assets"
385
-
386
-
387
- def load_performance_artifacts():
388
- """Load model-evaluation images generated by the evaluation notebook."""
389
 
390
- artifacts = {
391
- "unified_confusion_matrix": None,
392
- "melanoma_threshold_curve": None,
393
- "cluster_confusion_matrix": None,
394
- }
395
 
396
- if not ASSETS_DIR.is_dir():
397
- return artifacts
 
 
398
 
399
- files = {
400
- "unified_confusion_matrix": "unified_confusion_matrix.png",
401
- "melanoma_threshold_curve": "unified_melanoma_threshold_curve.png",
402
- "cluster_confusion_matrix": "cluster_confusion_matrix.png",
403
- }
404
-
405
- for key, filename in files.items():
406
- path = ASSETS_DIR / filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
- if path.is_file():
409
- artifacts[key] = str(path)
 
410
 
411
- return artifacts
412
 
413
 
414
  ABOUT_MARKDOWN = f"""
@@ -449,6 +466,18 @@ about what it isn't: a validated medical device.
449
  - 🟢 **Low** — typically manageable with general skin care. Doctor visit
450
  optional, especially if unsure or it's not improving.
451
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  {PAGE_DISCLAIMER}
453
  """
454
 
@@ -497,44 +526,21 @@ with gr.Blocks(title="Skin Disease Detection — Demo") as demo:
497
  ),
498
  )
499
 
500
- with gr.Tab("📊 Model Evaluation"):
501
- with gr.Accordion("ℹ️ About Model Evaluation", open=False):
502
- gr.HTML(data_badge(
503
- "🧪 All charts below are computed on <b>held-out test data</b> "
504
- "by <code>build_artifacts.py</code> — fully reproducible."
505
- ))
506
-
507
- artifacts = load_performance_artifacts()
508
-
509
- if not any(artifacts.values()):
510
-
511
  gr.Markdown(
512
- "### No model performance artifacts yet.\n\n"
513
- "Run the evaluation notebook and place the generated "
514
- "artifacts in the Space's `assets/` folder."
 
 
515
  )
516
-
517
  else:
518
-
519
- gr.Markdown("### Unified Model Evaluation")
520
-
521
- if artifacts["unified_confusion_matrix"]:
522
- gr.Image(
523
- value=artifacts["unified_confusion_matrix"],
524
- label="24-Class Confusion Matrix"
525
- )
526
-
527
- if artifacts["melanoma_threshold_curve"]:
528
- gr.Image(
529
- value=artifacts["melanoma_threshold_curve"],
530
- label="Melanoma Precision / Recall vs. Decision Threshold"
531
- )
532
-
533
- if artifacts["cluster_confusion_matrix"]:
534
- gr.Image(
535
- value=artifacts["cluster_confusion_matrix"],
536
- label="Disease Similarity Cluster Confusion Matrix"
537
- )
538
 
539
  with gr.Tab("ℹ️ About"):
540
  gr.Markdown(ABOUT_MARKDOWN)
@@ -545,4 +551,4 @@ with gr.Blocks(title="Skin Disease Detection — Demo") as demo:
545
  )
546
 
547
  if __name__ == "__main__":
548
- demo.launch()
 
23
  import requests
24
  from PIL import Image
25
  from transformers import pipeline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # ---------------------------------------------------------------------------
28
  # Config — override any of these via Space "Variables and secrets"
 
37
  ROUTER_URL = "https://router.huggingface.co/v1/chat/completions"
38
 
39
  PAGE_DISCLAIMER = (
40
+ "This tool is a research/portfolio project, not a medical device. It hasn't "
41
+ "been clinically validated. Use it to get informed and organize your thoughts "
42
+ "before a real appointment — not as a substitute for one."
43
  )
44
 
45
  # Optional second classifier specifically for pigmented-lesion cases (HAM10000-based,
 
176
  json={"model": CHAT_MODEL_ID, "messages": messages, "max_tokens": max_tokens},
177
  timeout=60,
178
  )
179
+ if not resp.ok:
180
+ # Surface the actual reason from the response body (e.g. "model not
181
+ # supported by any provider") instead of just the generic status line —
182
+ # that detail is what actually explains a 400/404, not "Bad Request".
183
+ try:
184
+ detail = resp.json()
185
+ except Exception:
186
+ detail = resp.text
187
+ raise RuntimeError(f"{resp.status_code} error from router for model '{CHAT_MODEL_ID}': {detail}")
188
  data = resp.json()
189
  return data["choices"][0]["message"]["content"]
190
 
 
373
  # ---------------------------------------------------------------------------
374
  # Model Performance tab data loading
375
  # ---------------------------------------------------------------------------
376
+ ASSETS_DIR = "assets"
 
 
 
 
 
377
 
 
 
 
 
 
378
 
379
+ def load_performance_reports():
380
+ """Scan assets/ for *_metrics.json files and pair each with its confusion-matrix
381
+ image if present. Returns a list of dicts; empty list if nothing's been uploaded yet."""
382
+ import json
383
 
384
+ reports = []
385
+ if not os.path.isdir(ASSETS_DIR):
386
+ return reports
387
+ for fname in sorted(os.listdir(ASSETS_DIR)):
388
+ if not fname.endswith("_metrics.json"):
389
+ continue
390
+ prefix = fname[: -len("_metrics.json")]
391
+ try:
392
+ with open(os.path.join(ASSETS_DIR, fname)) as f:
393
+ data = json.load(f)
394
+ except Exception:
395
+ continue
396
+ image_path = None
397
+ for ext in (".png", ".jpg", ".jpeg"):
398
+ candidate = os.path.join(ASSETS_DIR, f"{prefix}_confusion_matrix{ext}")
399
+ if os.path.isfile(candidate):
400
+ image_path = candidate
401
+ break
402
+ reports.append({"data": data, "image_path": image_path})
403
+ return reports
404
+
405
+
406
+ def format_performance_markdown(report):
407
+ data = report["data"]
408
+ lines = [f"### {data.get('model_name', 'Model')}"]
409
+ acc = data.get("accuracy")
410
+ f1m = data.get("f1_macro")
411
+ if acc is not None:
412
+ lines.append(f"**Accuracy:** {acc*100:.1f}% · **Macro F1:** {f1m*100:.1f}%" if f1m is not None else f"**Accuracy:** {acc*100:.1f}%")
413
+
414
+ per_class = data.get("per_class", {})
415
+ if per_class:
416
+ lines.append("\n| Class | Precision | Recall | F1 | Support |")
417
+ lines.append("|---|---|---|---|---|")
418
+ for cls, m in per_class.items():
419
+ lines.append(
420
+ f"| {cls} | {m.get('precision', 0)*100:.1f}% | {m.get('recall', 0)*100:.1f}% "
421
+ f"| {m.get('f1', 0)*100:.1f}% | {int(m.get('support', 0))} |"
422
+ )
423
 
424
+ notes = data.get("notes")
425
+ if notes:
426
+ lines.append(f"\n_Note: {notes}_")
427
 
428
+ return "\n".join(lines)
429
 
430
 
431
  ABOUT_MARKDOWN = f"""
 
466
  - 🟢 **Low** — typically manageable with general skin care. Doctor visit
467
  optional, especially if unsure or it's not improving.
468
 
469
+ ### Known limitations (read this before trusting the output)
470
+ - **Not clinically validated.** Benchmark accuracy on a public dataset is
471
+ not the same as real-world diagnostic reliability.
472
+ - **Dataset label limitations.** The DermNet taxonomy combines melanoma
473
+ with benign moles under one label — the app defaults that whole category
474
+ to High severity rather than pretending to distinguish them.
475
+ - **Skin-tone representation.** Public dermatology datasets skew toward
476
+ lighter skin tones; performance on underrepresented skin tones is
477
+ unverified without a dedicated bias audit.
478
+ - **No clinical validation, IRB review, or regulatory clearance** has been
479
+ done. This should inform, not replace, professional care.
480
+
481
  {PAGE_DISCLAIMER}
482
  """
483
 
 
526
  ),
527
  )
528
 
529
+ with gr.Tab("📊 Model Performance"):
530
+ performance_reports = load_performance_reports()
531
+ if not performance_reports:
 
 
 
 
 
 
 
 
532
  gr.Markdown(
533
+ "_No model performance data yet._\n\n"
534
+ "Run either fine-tuning notebook in this repo, then download the "
535
+ "`*_metrics.json` and `*_confusion_matrix.png` files it produces and place "
536
+ "them in this Space's `assets/` folder. This tab picks them up automatically "
537
+ "on the next restart."
538
  )
 
539
  else:
540
+ for report in performance_reports:
541
+ gr.Markdown(format_performance_markdown(report))
542
+ if report["image_path"]:
543
+ gr.Image(value=report["image_path"], label="Confusion matrix (test set)", show_label=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
 
545
  with gr.Tab("ℹ️ About"):
546
  gr.Markdown(ABOUT_MARKDOWN)
 
551
  )
552
 
553
  if __name__ == "__main__":
554
+ demo.launch()