tod / app.py
trapezius60's picture
Update app.py
397c847 verified
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()