Spaces:
Running
Running
| 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('<a href="https://docs.google.com/forms/d/e/1FAIpQLSc2xKfhGo5fsyTiq8CcdSc_PelNbMpo42HbaehGhghnqqPz0g/viewform" target="_blank">💬 Send Feedback</a>') | |
| gr.Markdown("App version: 1.0.0 | Updated: August 2025 | Powered by Dr. BH") | |
| if __name__ == "__main__": | |
| demo.launch() |