Spaces:
Sleeping
Sleeping
| """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)}**") | |