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