Spaces:
Sleeping
Sleeping
File size: 5,616 Bytes
b0c3a57 5b7432c b0c3a57 1f7c87f b0c3a57 5b7432c 1f7c87f 5b7432c 1f7c87f 5b7432c 1f7c87f 5b7432c b0c3a57 5b7432c b0c3a57 1f7c87f b0c3a57 5b7432c 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 1f7c87f b0c3a57 5b7432c b0c3a57 5b7432c 1f7c87f 5b7432c 1f7c87f 5b7432c 1f7c87f b0c3a57 1f7c87f 5b7432c 1f7c87f b0c3a57 1f7c87f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
"""OphthalmoCapture — Labeling Component
Provides:
1. Categorical radio selector: Normal / Cataract / Bad quality / Needs dilation
2. LOCS III dropdowns (only when "Cataract" is selected):
- Nuclear Opalescence (NO) 0-6
- Nuclear Color (NC) 0-6
- Cortical Opacity (C) 0-5
3. Auto-saves (upsert) to audit DB on every change.
Numeric values are stored for ML; only text labels are shown in the UI.
"""
import streamlit as st
import config
import database as db
from i18n import t, label_display, label_from_display, locs_display
from services import session_manager as sm
def _save_to_db(img: dict, image_id: str):
"""Persist current label + LOCS data to audit DB (non-blocking)."""
try:
db.save_or_update_annotation(
image_filename=img["filename"],
label=img["label"],
transcription=img.get("transcription", ""),
doctor_name=st.session_state.get("doctor_name", ""),
session_id=st.session_state.get("session_id", ""),
locs_data=img.get("locs_data", {}),
)
except Exception:
pass
def _render_locs_dropdown(field: dict, image_id: str, current_locs: dict) -> int | None:
"""Render a single LOCS dropdown and return the selected numeric value."""
field_id = field["field_id"]
options = field["options"]
display_labels = [locs_display(opt["display"]) for opt in options]
# Determine current index from stored data
stored_value = current_locs.get(field_id)
if stored_value is not None:
current_index = next(
(i for i, opt in enumerate(options) if opt["value"] == stored_value),
None,
)
else:
current_index = None
# Use index=None so nothing is pre-selected until doctor chooses
selected_display = st.selectbox(
locs_display(field["label"]),
display_labels,
index=current_index,
key=f"locs_{field_id}_{image_id}",
placeholder=t("locs_placeholder"),
)
if selected_display is not None and selected_display in display_labels:
idx = display_labels.index(selected_display)
return options[idx]["value"]
return None
def render_labeler(image_id: str):
"""Render the full labeling panel for the given image."""
img = st.session_state.images.get(image_id)
if img is None:
return
st.subheader(t("labeling"))
# ── 1. Categorical classification ────────────────────────────────────
# Translated display (UI only); storage always uses English name.
translated_options = [label_display(opt["display"]) for opt in config.LABEL_OPTIONS]
current_label = img.get("label") # English, e.g. "Cataract"
if current_label is not None:
translated_current = label_display(current_label)
if translated_current in translated_options:
current_index = translated_options.index(translated_current)
else:
current_index = None
else:
current_index = None
with st.container(border=True):
if current_index is None:
st.caption(t("select_label_hint"))
selected = st.radio(
t("classification"),
translated_options,
index=current_index,
key=f"label_radio_{image_id}",
horizontal=True,
label_visibility="collapsed",
)
# Map translated selection back to English for storage
new_label = label_from_display(selected) if selected in translated_options else None
# Detect categorical change
label_changed = new_label is not None and new_label != current_label
if label_changed:
img["label"] = new_label
img["labeled_by"] = st.session_state.get("doctor_name", "")
# If switching away from Cataract, clear LOCS data
if new_label != "Cataract":
img["locs_data"] = {}
sm.update_activity()
_save_to_db(img, image_id)
# ── 2. LOCS III Classification (only for "Cataract") ─────────────────
effective_label = new_label or current_label
if effective_label == "Cataract":
st.markdown("---")
st.markdown(t("locs_title"))
current_locs = img.get("locs_data", {})
locs_changed = False
with st.container(border=True):
for field_def in config.LOCS_FIELDS:
value = _render_locs_dropdown(field_def, image_id, current_locs)
field_id = field_def["field_id"]
if value is not None and value != current_locs.get(field_id):
current_locs[field_id] = value
locs_changed = True
img["locs_data"] = current_locs
if locs_changed:
sm.update_activity()
_save_to_db(img, image_id)
# LOCS summary
filled = sum(1 for f in config.LOCS_FIELDS if f["field_id"] in current_locs)
total_fields = len(config.LOCS_FIELDS)
if filled < total_fields:
st.info(t("locs_progress", filled=filled, total=total_fields))
else:
st.success(t("locs_complete", filled=filled, total=total_fields))
# ── 3. Visual feedback ───────────────────────────────────────────────────
if effective_label is None:
st.warning(t("unlabeled"))
else:
st.success(f"{t('label_set')}: **{label_display(effective_label)}**")
|