her222 / app.py
danieletx's picture
Upload 4 files
e4193e2 verified
import gradio as gr
import pandas as pd
import tempfile
def clasificar_her2(ratio, her2_por_celula):
if ratio >= 2.0 and her2_por_celula >= 4.0:
return 1, "POSITIVO", "RATIO ≥2.0 y HER2 ≥4. Amplificación verdadera.", "pos"
if ratio >= 2.0 and her2_por_celula < 4.0:
return 2, "NEGATIVO", "RATIO ≥2.0 pero HER2 <4. Monosomía 17.", "neg"
if ratio < 2.0 and her2_por_celula >= 6.0:
return 3, "POSITIVO", "HER2 ≥6 con RATIO <2. Amplificación verdadera.", "pos"
if ratio < 2.0 and 4.0 <= her2_por_celula < 6.0:
return 4, "NEGATIVO (salvo IHQ 3+)", "HER2 4–6 con RATIO <2. Correlación necesaria.", "border"
if ratio < 2.0 and her2_por_celula < 4.0:
return 5, "NEGATIVO", "RATIO <2 y HER2 <4. No amplificado.", "neg"
return '-', 'PENDIENTE', '-', 'muted'
def pill_html(text, status):
palette = {
'pos': {'bg': '#E6F4EA', 'fg': '#1F8F3E', 'bd': '#A5D6A7'},
'neg': {'bg': '#FDECEA', 'fg': '#C62828', 'bd': '#EF9A9A'},
'border': {'bg': '#FFF4E5', 'fg': '#EF6C00', 'bd': '#FFCC80'},
'muted': {'bg': '#F3F4F6', 'fg': '#374151', 'bd': '#E5E7EB'},
}
c = palette.get(status, palette['muted'])
return f"<span style='padding:6px 10px;border-radius:8px;border:1px solid {c['bd']};background:{c['bg']};color:{c['fg']};font-weight:600;'>{text}</span>"
def validate_signal(value, name):
if value is None:
return False, f"{name}: valor vacío.", None
try:
iv = int(value)
except:
return False, f"{name}: debe ser entero (0–20).", None
if iv < 0 or iv > 20:
return False, f"{name}: fuera de rango (0–20).", None
return True, '', iv
def dataframe_from_rows(rows):
return pd.DataFrame(rows) if rows else pd.DataFrame(columns=['HER2', 'CEN17'])
def recomputar(rows, objetivo):
df = dataframe_from_rows(rows)
n = len(df)
aviso = ''
if n > 0:
her2_mean = df['HER2'].mean()
cen17_mean = df['CEN17'].mean()
ratio = her2_mean / cen17_mean if cen17_mean > 0 else 0
if cen17_mean == 0:
aviso = 'Advertencia: CEN17 media = 0; ratio no evaluable.'
else:
her2_mean = cen17_mean = ratio = 0
if n >= objetivo and cen17_mean > 0:
grupo, interpretacion, comentario, status = clasificar_her2(ratio, her2_mean)
int_html = pill_html(interpretacion, status)
else:
grupo = '-'
comentario = '-'
int_html = pill_html('PENDIENTE (requiere completar células)', 'muted')
progreso = f"{n}/{objetivo}"
return rows, df, progreso, round(her2_mean,3), round(cen17_mean,3), round(ratio,3), grupo, int_html, comentario, aviso
def agregar_contaje(her2, cen17, rows, objetivo):
rows = rows or []
ok1, msg1, v1 = validate_signal(her2, 'HER2')
ok2, msg2, v2 = validate_signal(cen17, 'CEN17')
if not ok1 or not ok2:
aviso = ' ; '.join(m for m in [msg1, msg2] if m)
r = list(recomputar(rows, objetivo))
r[-1] = aviso
return tuple(r)
rows.append({'HER2': v1, 'CEN17': v2})
return recomputar(rows, objetivo)
def borrar_ultima(rows, objetivo):
rows = rows or []
if rows:
rows = rows[:-1]
return recomputar(rows, objetivo)
def borrar_todas(rows, objetivo):
rows = []
r = list(recomputar(rows, objetivo))
r[-1] = 'Se han borrado todas las células.'
return tuple(r)
def generar_resumen(rows, objetivo):
rows, df, progreso, h, c, r, g, i, co, av = recomputar(rows, objetivo)
texto = [
'RESUMEN HER2 FISH (ASCO/CAP 2018)',
'--------------------------------------
',
f'Células evaluadas: {progreso}',
f'HER2/célula: {h}',
f'CEN17/célula: {c}',
f'RATIO: {r}',
f'Grupo FISH: {g}',
f'Interpretación: {i}',
f'Comentario: {co}'
]
if av:
texto.append(f'Aviso: {av}')
texto.append('
Contajes:')
texto.append(df.to_string(index=False))
contenido = '
'.join(texto)
tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.txt', mode='w', encoding='utf-8')
tmp.write(contenido)
tmp.close()
return tmp.name
def build_ui():
with gr.Blocks(title='Calculadora HER2 FISH ASCO/CAP 2018') as demo:
gr.Markdown('## 🧮 Calculadora FISH HER2 (ASCO/CAP 2018) — Gradio 6.x')
objetivo = gr.Radio([20,40], value=20, label='Células a evaluar')
her2 = gr.Number(label='HER2 (señales)', precision=0)
cen17 = gr.Number(label='CEN17 (señales)', precision=0)
df_state = gr.State([])
btn_add = gr.Button('Agregar célula')
btn_del = gr.Button('Borrar última')
btn_clr = gr.Button('Borrar todas')
btn_res = gr.Button('📄 Generar resumen imprimible')
tabla = gr.Dataframe(headers=['HER2','CEN17'], interactive=False, height=250)
progreso = gr.Textbox(label='Progreso')
hmean = gr.Number(label='HER2/célula')
cmean = gr.Number(label='CEN17/célula')
ratio = gr.Number(label='RATIO')
grupo = gr.Textbox(label='Grupo')
interpretacion = gr.HTML(label='Interpretación')
comentario = gr.Textbox(label='Comentario')
aviso = gr.Textbox(label='Aviso')
resumen = gr.File(label='Descargar resumen')
outputs = [df_state, tabla, progreso, hmean, cmean, ratio, grupo, interpretacion, comentario, aviso]
btn_add.click(agregar_contaje, inputs=[her2, cen17, df_state, objetivo], outputs=outputs)
btn_del.click(borrar_ultima, inputs=[df_state, objetivo], outputs=outputs)
btn_clr.click(borrar_todas, inputs=[df_state, objetivo], outputs=outputs)
objetivo.change(recomputar, inputs=[df_state, objetivo], outputs=outputs)
btn_res.click(generar_resumen, inputs=[df_state, objetivo], outputs=resumen)
return demo
if __name__ == '__main__':
ui = build_ui()
ui.launch(server_name='0.0.0.0', server_port=7860)