import gradio as gr # --- Decomposition levels mapping --- decomposition_map = { "ไม่เน่า": None, "ท้องเขียว": (24, None), "Marbling": (48, None), "Bloating": (72, 120), "Facial bone exposure": (168, None), "Chest/abdomen organ exposure": (336, 672), "Nearly full skeleton": (2160, 4320), "Full skeleton": (4320, 8640) } # --- Time formatter --- def format_time(hours): if hours is None: return None if hours < 24: return f"{hours} ชั่วโมง" days = hours / 24 if days < 7: return f"{days:.0f} วัน" weeks = days / 7 if weeks < 4: return f"{weeks:.0f} สัปดาห์" months = weeks / 4.345 return f"{months:.0f} เดือน" # --- Combine parameters and detect conflicts --- def combine_params(params, enforce_cap24=False): lowers = [lo for _, lo, _, _ in params if lo is not None] uppers = [up for _, _, up, _ in params if up is not None] final_lower = max(lowers) if lowers else 0 final_upper = min(uppers) if uppers else None if final_upper is not None and final_lower > final_upper: conflicting = [name for name, lo, up, _ in params if (lo is not None and lo > final_upper) or (up is not None and up < final_lower)] raise ValueError(f"!!! พารามิเตอร์ขัดแย้งกัน: {', '.join(conflicting)}") if enforce_cap24 and (final_upper is None or final_upper > 24): final_upper = 24 details = "\n".join(detail for _, _, _, detail in params) return final_lower, final_upper, details # --- TOD estimation logic --- def estimate_tod_gui(decomposition, biceps_contraction, rigor_mortis, livor_mortis): params = [] if decomposition != "ไม่เน่า": lo, up = decomposition_map.get(decomposition, (None, None)) explanation = f"{decomposition} → {format_time(lo)} ขึ้นไป" if lo else f"{decomposition}" explanation += "\nIf decomposition present, ignore other parameters" params.append((f"Decomposition: {decomposition}", lo, up, explanation)) return combine_params(params, enforce_cap24=False) if biceps_contraction == 'Positive': params.append(("Biceps contraction", 0, 5, "Biceps contraction → ≤ 5 ชั่วโมง")) elif biceps_contraction == 'Negative': params.append(("Biceps contraction", 5, None, "Biceps contraction negative → ≥ 5 ชั่วโมง")) if rigor_mortis == 'No': params.append(("Rigor mortis", 0, 2, "No rigor → ≤ 2 ชั่วโมง")) elif rigor_mortis == 'Partial': params.append(("Rigor mortis", 2, 12, "Partial rigor → 2–12 ชั่วโมง")) elif rigor_mortis == 'Full': params.append(("Rigor mortis", 6, 24, "Full rigor → 6–12 ชั่วโมง แข็งต่อเนื่องอีก 12 ชั่วโมง")) elif rigor_mortis == 'Starting to release': params.append(("Rigor mortis", 18, 24, "Starting to release → 18–24 ชั่วโมง")) if livor_mortis == 'No': params.append(("Livor mortis", 0, 0.5, "No livor → ≤ 0.5 ชั่วโมง")) elif livor_mortis == 'Yes, Not fixed': params.append(("Livor mortis", 0.5, 12, "Livor not fixed → 0.5–12 ชั่วโมง")) elif livor_mortis == 'Yes, Not fixed, Confluence': params.append(("Livor mortis", 2, 12, "Livor not fixed and Confluence → 2–12 ชั่วโมง")) elif livor_mortis == 'Yes, Fixed': params.append(("Livor mortis", 8, 24, "Livor fixed → ≥ 8 ชั่วโมง")) return combine_params(params, enforce_cap24=True) # --- Gradio interface function --- def predict(decomp, biceps, rigor, livor): try: lo, up, explanation = estimate_tod_gui(decomp, biceps, rigor, livor) lo_str = format_time(lo) up_str = format_time(up) if up is not None else None if up_str is None: result_text = f"ประมาณ: ≥ {lo_str}\n\n{explanation}" else: result_text = f"ประมาณ: {lo_str} – {up_str}\n\n{explanation}" return result_text except ValueError as e: return str(e) # --- Gradio UI --- with gr.Blocks(css="style.css") as demo: gr.Markdown("## 🕒 Time of Death Estimation Tool") gr.Markdown("Estimate postmortem interval based on forensic indicators.") with gr.Row(): with gr.Column(): decomp_in = gr.Dropdown(list(decomposition_map.keys()), label="🧟 Decomposition", value="ไม่เน่า") biceps_in = gr.Dropdown(["Positive", "Negative"], label="💪 Biceps contraction", value="Positive") rigor_in = gr.Dropdown(["No", "Partial", "Full", "Starting to release"], label="🧊 Rigor mortis", value="No") livor_in = gr.Dropdown(["No", "Yes, Not fixed", "Yes, Not fixed, Confluence", "Yes, Fixed"], label="🩸 Livor mortis", value="No") calculate_btn = gr.Button("🔍 Calculate") with gr.Column(): output_box = gr.Textbox(label="🧾 Result", lines=10, elem_id="result_box") calculate_btn.click( predict, inputs=[decomp_in, biceps_in, rigor_in, livor_in], outputs=output_box ) gr.Markdown("---") gr.Markdown('💬 Send Feedback') gr.Markdown("App version: 1.0.0 | Updated: August 2025 | Powered by Dr. BH") if __name__ == "__main__": demo.launch()