File size: 18,578 Bytes
e36bc3e | 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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | """
๐ฌ AI-Powered Forensic Triage & Postmortem Intelligence System
"""
import gradio as gr
import pandas as pd
import numpy as np
import sys
import os
from datetime import datetime
from typing import Dict
import warnings
warnings.filterwarnings("ignore")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from modules.autopsy_analyzer import AutopsyAnalyzer
from modules.tod_estimator import TimeOfDeathEstimator
from modules.digital_evidence import DigitalEvidenceCorrelator
from modules.risk_scorer import CaseRiskScorer
from modules.timeline_builder import TimelineBuilder
autopsy_analyzer = AutopsyAnalyzer()
tod_estimator = TimeOfDeathEstimator()
evidence_correlator = DigitalEvidenceCorrelator()
risk_scorer = CaseRiskScorer()
timeline_builder = TimelineBuilder()
FORENSIC_CSS = """
.gradio-container { max-width: 1400px !important; }
.disclaimer-box { background: #1c1c1c; border: 1px solid #f85149; border-radius: 8px; padding: 12px; margin: 10px 0; font-size: 0.85em; color: #ffa657; }
.header-box { text-align: center; padding: 20px; background: linear-gradient(135deg, #0d1117 0%, #161b22 100%); border-radius: 12px; margin-bottom: 20px; border: 1px solid #30363d; }
"""
# โโโ Demo Data โโโ
DEMO_REPORT = """AUTOPSY REPORT - CASE #2024-MH-0847
DECEDENT: John Doe (unidentified male)
AGE: Approximately 35-45 years
DATE OF EXAMINATION: March 15, 2024, 09:00 AM
DATE BODY FOUND: March 14, 2024, 06:30 PM
LOCATION: Abandoned warehouse, Industrial District, Block 7
EXTERNAL EXAMINATION:
The body is that of a well-nourished adult male, approximately 5'10" tall, weighing approximately 78 kg. Rigor mortis is fully developed in all extremities. Lividity is fixed and posterior, consistent with supine position. No signs of decomposition noted.
Body temperature (rectal) measured at 28.5ยฐC at time of discovery. Ambient temperature at scene: 18ยฐC.
INJURIES:
1. Blunt force trauma to the right temporal region, measuring 4.5 x 3.2 cm, with underlying subdural hematoma.
2. Defensive wounds on both forearms - three linear abrasions (2-4 cm each).
3. Contusion on the left shoulder, 6 x 4 cm, consistent with forceful impact.
4. Petechial hemorrhages noted in conjunctivae bilaterally.
5. Ligature mark around the neck, 0.5 cm width, horizontal orientation with slight upward angle posteriorly.
INTERNAL EXAMINATION:
- Subdural hematoma (right hemisphere, approximately 80ml)
- Cerebral edema noted
- Hyoid bone intact
- Lungs show mild congestion
- Heart: 340g, no significant atherosclerosis
- Liver: mild fatty changes
- Stomach contents: partially digested meal (rice, vegetables), approximately 200ml
TOXICOLOGY:
- Blood alcohol: 0.04 g/dL
- Benzodiazepines: detected (trace levels - diazepam)
- No illicit substances detected
CAUSE OF DEATH: Combination of blunt force head trauma with subdural hematoma and asphyxia due to ligature compression of neck.
MANNER OF DEATH: Homicide
ADDITIONAL NOTES:
- Skin under fingernails collected for DNA analysis
- Foreign fibers recovered from ligature site (synthetic, blue)
- Time of death estimated between 12-18 hours prior to discovery based on postmortem changes"""
DEMO_EVIDENCE = """timestamp,source,event_type,location_lat,location_lon,details
2024-03-14 00:30,CCTV-Cam7,vehicle_detected,19.0760,72.8777,White sedan entering industrial area
2024-03-14 01:15,Mobile-Tower,cell_ping,19.0755,72.8780,Victim phone connected to tower ID-4421
2024-03-14 01:45,CCTV-Cam12,person_detected,19.0762,72.8775,Two individuals walking toward warehouse
2024-03-14 02:00,Mobile-Tower,cell_ping,19.0762,72.8774,Victim phone - last active ping
2024-03-14 02:15,CCTV-Cam12,person_detected,19.0762,72.8775,Single individual leaving warehouse rapidly
2024-03-14 02:20,CCTV-Cam7,vehicle_detected,19.0760,72.8777,White sedan exiting industrial area at high speed
2024-03-14 02:30,Mobile-Tower,cell_disconnect,19.0762,72.8774,Victim phone disconnected
2024-03-14 06:00,CCTV-Cam7,person_detected,19.0758,72.8779,Security guard patrol - routine
2024-03-14 18:30,Emergency,call_received,19.0762,72.8775,Body discovered by security guard"""
# โโโ Handler Functions โโโ
def analyze_autopsy_report(report_file, report_text, state):
try:
if report_file is not None:
text = autopsy_analyzer.extract_text_from_file(report_file)
elif report_text and report_text.strip():
text = report_text.strip()
else:
return ([("Please upload a file or paste report text.", None)],
pd.DataFrame(columns=["Entity", "Category", "Confidence", "Context"]),
"## โ ๏ธ No input provided", state)
results = autopsy_analyzer.analyze(text)
state = state or {}
state["report_text"] = text
state["entities"] = results["entities"]
state["report_summary"] = results["summary"]
state["timeline_events"] = state.get("timeline_events", []) + results.get("time_events", [])
return (results["highlighted_text"], pd.DataFrame(results["entities_table"]),
results["summary_markdown"], state)
except Exception as e:
return [("Error.", None)], pd.DataFrame(), f"## โ Error\n{e}", state
def calculate_tod(t_rectal, t_ambient, body_weight, corrective_str,
rigor, lividity, decomp, humidity, wind, state):
try:
corrective = float(corrective_str.split(" - ")[0])
results = tod_estimator.estimate(t_rectal=t_rectal, t_ambient=t_ambient,
body_weight=body_weight, corrective_factor=corrective,
rigor=rigor, lividity=lividity, decomp=decomp,
humidity=humidity, wind_speed=wind)
cooling_plot = tod_estimator.plot_cooling_curve(t_rectal, t_ambient, body_weight, corrective)
state = state or {}
state["tod_estimate"] = results["summary"]
state["timeline_events"] = state.get("timeline_events", []) + [{
"event": f"TOD: ~{results['summary'].get('estimated_pmi_hours', '?')}h before discovery",
"category": "TOD Window", "source": "Henssge Model", "timestamp": datetime.now().isoformat()
}]
return results["summary"], cooling_plot, results["detail_markdown"], state
except Exception as e:
return {"error": str(e)}, None, f"## โ Error\n{e}", state
def correlate_evidence(evidence_file, manual_entries, state):
try:
state = state or {}
if evidence_file is not None:
results = evidence_correlator.analyze_from_file(evidence_file)
elif manual_entries and manual_entries.strip():
results = evidence_correlator.analyze_from_text(manual_entries)
else:
return pd.DataFrame(), None, "## โ ๏ธ No data provided", state
state["digital_evidence"] = results["evidence_records"]
state["timeline_events"] = state.get("timeline_events", []) + results.get("timeline_events", [])
state["correlations"] = results.get("correlations", [])
return results["evidence_table"], results["correlation_plot"], results["analysis_markdown"], state
except Exception as e:
return pd.DataFrame(), None, f"## โ Error\n{e}", state
def compute_risk(state):
try:
state = state or {}
if not any(k in state for k in ["entities", "tod_estimate", "digital_evidence"]):
return (0, {"LOW": 1.0}, None,
"## โ ๏ธ Insufficient Data\nAnalyze evidence in other tabs first.", state)
results = risk_scorer.compute_risk(state)
state["risk_score"] = results["risk_score"]
state["anomalies"] = results.get("anomalies", [])
return (results["risk_score"], results["risk_classification"],
results["anomaly_plot"], results["explanation_markdown"], state)
except Exception as e:
return 0, {"ERROR": 1.0}, None, f"## โ Error\n{e}", state
def build_timeline(state):
try:
state = state or {}
if not state.get("timeline_events"):
return None, pd.DataFrame(), "## โ ๏ธ No timeline data"
results = timeline_builder.build(state)
return results["timeline_plot"], results["timeline_table"], results["summary_markdown"]
except Exception as e:
return None, pd.DataFrame(), f"## โ Error\n{e}"
# โโโ Build App โโโ
def create_app():
with gr.Blocks(title="๐ฌ Forensic Triage & Postmortem Intelligence System",
theme=gr.themes.Soft(primary_hue="red", secondary_hue="blue", neutral_hue="slate"),
css=FORENSIC_CSS) as demo:
case_state = gr.State({})
gr.HTML("""<div class="header-box">
<h1>๐ฌ AI-Powered Forensic Triage & Postmortem Intelligence System</h1>
<p style="color: #8b949e; font-size: 1.1em;">Intelligent Investigative Support โข Evidence Analysis โข Digital Correlation โข Risk Assessment</p>
</div>""")
gr.HTML("""<div class="disclaimer-box">
โ ๏ธ <strong>DISCLAIMER:</strong> This is strictly an investigative assistance platform โ NOT a replacement for forensic experts or legal authorities. All outputs support human decision-making only.
</div>""")
with gr.Tabs():
# โโโ TAB 1: Autopsy Report โโโ
with gr.Tab("๐ Autopsy Report Analysis"):
gr.Markdown("### AI-Based Autopsy Report Analysis\nExtract forensic entities, injury patterns, and key findings using NLP.")
with gr.Row():
with gr.Column(scale=1):
report_file = gr.File(label="๐ Upload Report (PDF/TXT)", file_types=[".pdf", ".txt"], type="filepath")
report_text = gr.Textbox(label="๐ Or Paste Report Text", lines=12, placeholder="Paste autopsy report...")
with gr.Row():
analyze_btn = gr.Button("๐ Analyze Report", variant="primary", scale=2)
demo_btn = gr.Button("๐ Load Demo", variant="secondary", scale=1)
with gr.Column(scale=2):
ner_output = gr.HighlightedText(label="๐ท๏ธ Extracted Entities", show_legend=True, combine_adjacent=True,
color_map={"CAUSE_OF_DEATH": "#f85149", "INJURY": "#ff7b72", "TOXICOLOGY": "#ffa657",
"TIME_INDICATOR": "#79c0ff", "ANATOMICAL": "#56d364", "MEDICAL_FINDING": "#d2a8ff",
"MANNER_OF_DEATH": "#f47067", "DEMOGRAPHIC": "#e3b341", "LOCATION": "#a5d6ff", "EVIDENCE": "#7ee787"})
entities_table = gr.DataFrame(label="๐ Structured Entities")
report_summary = gr.Markdown(label="๐ Summary")
analyze_btn.click(fn=analyze_autopsy_report, inputs=[report_file, report_text, case_state],
outputs=[ner_output, entities_table, report_summary, case_state])
demo_btn.click(fn=lambda: DEMO_REPORT, inputs=[], outputs=[report_text])
# โโโ TAB 2: TOD Estimation โโโ
with gr.Tab("โฑ๏ธ Time-of-Death Estimation"):
gr.Markdown("### Post-Mortem Interval (PMI) Estimation\nHenssge nomogram + postmortem indicators.")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("#### ๐ก๏ธ Temperature")
t_rectal = gr.Slider(15, 37.2, value=28.5, step=0.1, label="Rectal Temp (ยฐC)")
t_ambient = gr.Slider(-10, 45, value=18, step=0.5, label="Ambient Temp (ยฐC)")
body_weight = gr.Slider(20, 180, value=78, step=1, label="Body Weight (kg)")
corrective = gr.Dropdown(choices=["1.0 - Naked", "0.75 - Thin layers", "0.9 - Light clothing",
"1.1 - Thicker layers", "1.2 - 2-3 layers", "1.3 - Heavy clothing",
"0.5 - Moving water", "0.7 - Still water", "0.35 - Flowing water"],
value="0.9 - Light clothing", label="Corrective Factor")
gr.Markdown("#### ๐ฌ Postmortem Signs")
rigor = gr.Radio(["absent", "developing", "full", "resolving"], value="full", label="Rigor Mortis")
lividity = gr.Radio(["absent", "developing", "present_movable", "fixed"], value="fixed", label="Lividity")
decomp = gr.Radio(["absent", "early_discoloration", "bloating", "advanced"], value="absent", label="Decomposition")
gr.Markdown("#### ๐ค๏ธ Environment")
humidity = gr.Slider(0, 100, value=65, step=5, label="Humidity (%)")
wind = gr.Slider(0, 50, value=5, step=1, label="Wind Speed (km/h)")
calc_btn = gr.Button("โฑ๏ธ Calculate PMI", variant="primary")
with gr.Column(scale=2):
pmi_result = gr.JSON(label="๐ PMI Results")
cooling_plot = gr.Plot(label="๐ก๏ธ Cooling Curve")
tod_detail = gr.Markdown(label="๐ Detailed Analysis")
calc_btn.click(fn=calculate_tod,
inputs=[t_rectal, t_ambient, body_weight, corrective, rigor, lividity, decomp, humidity, wind, case_state],
outputs=[pmi_result, cooling_plot, tod_detail, case_state])
# โโโ TAB 3: Digital Evidence โโโ
with gr.Tab("๐ฑ Digital Evidence Correlation"):
gr.Markdown("### Digital Evidence Analysis\nCorrelate CCTV, mobile, and geolocation data.")
with gr.Row():
with gr.Column(scale=1):
evidence_file = gr.File(label="๐ Upload CSV", file_types=[".csv"], type="filepath")
gr.Markdown("**CSV format:** `timestamp, source, event_type, location_lat, location_lon, details`")
manual_evidence = gr.Textbox(label="๐ Or Enter CSV Data", lines=8, placeholder="timestamp,source,event_type,...")
with gr.Row():
correlate_btn = gr.Button("๐ Correlate", variant="primary", scale=2)
demo_ev_btn = gr.Button("๐ Demo", variant="secondary", scale=1)
with gr.Column(scale=2):
evidence_table = gr.DataFrame(label="๐ Evidence Log")
evidence_plot = gr.Plot(label="๐ Correlation Timeline")
evidence_md = gr.Markdown(label="๐ Analysis")
correlate_btn.click(fn=correlate_evidence, inputs=[evidence_file, manual_evidence, case_state],
outputs=[evidence_table, evidence_plot, evidence_md, case_state])
demo_ev_btn.click(fn=lambda: DEMO_EVIDENCE, inputs=[], outputs=[manual_evidence])
# โโโ TAB 4: Risk Scoring โโโ
with gr.Tab("โ ๏ธ Risk & Anomaly Detection"):
gr.Markdown("### Case Risk Scoring & Anomaly Detection\nMulti-factor risk assessment from all evidence.")
gr.HTML('<div class="disclaimer-box">โน๏ธ Requires data from other tabs for comprehensive assessment.</div>')
score_btn = gr.Button("๐งฎ Compute Risk Score", variant="primary", size="lg")
with gr.Row():
with gr.Column(scale=1):
risk_score_out = gr.Number(label="Risk Score (0-100)", precision=1)
risk_label_out = gr.Label(label="Classification")
with gr.Column(scale=2):
anomaly_plot_out = gr.Plot(label="๐ Risk Factors")
risk_md_out = gr.Markdown(label="๐ Assessment Report")
score_btn.click(fn=compute_risk, inputs=[case_state],
outputs=[risk_score_out, risk_label_out, anomaly_plot_out, risk_md_out, case_state])
# โโโ TAB 5: Timeline โโโ
with gr.Tab("๐
Investigation Timeline"):
gr.Markdown("### Integrated Investigation Timeline\nAll evidence sources consolidated.")
timeline_btn = gr.Button("๐
Build Timeline", variant="primary", size="lg")
timeline_plot_out = gr.Plot(label="๐
Timeline")
timeline_table_out = gr.DataFrame(label="๐ Events")
timeline_md_out = gr.Markdown(label="๐ Summary")
timeline_btn.click(fn=build_timeline, inputs=[case_state],
outputs=[timeline_plot_out, timeline_table_out, timeline_md_out])
# โโโ TAB 6: About โโโ
with gr.Tab("โน๏ธ About"):
gr.Markdown("""
## ๐ฌ AI-Powered Forensic Triage & Postmortem Intelligence System
### Capabilities
| Module | Description |
|--------|-------------|
| ๐ Autopsy NLP | Entity extraction from unstructured reports |
| โฑ๏ธ TOD Estimation | Henssge nomogram + postmortem indicators |
| ๐ฑ Digital Evidence | CCTV/mobile/geolocation correlation |
| โ ๏ธ Risk Scoring | Multi-factor assessment + anomaly detection |
| ๐
Timeline | Integrated evidence visualization |
### Methodology
- **Henssge (1988)**: Double-exponential cooling model for PMI
- **Pattern NLP**: Forensic-domain regex entity extraction
- **Risk Engine**: Weighted multi-factor scoring (violence, gaps, toxicology, patterns)
- **Anomaly Detection**: Cross-factor inconsistency identification
### Ethics & Legal
- โ๏ธ Not a replacement for experts or legal authorities
- ๐ Data privacy is the responsibility of operating agencies
- ๐ All outputs include reasoning for transparency
### Future Scope
- GLiNER-BioMed zero-shot entity extraction
- Forensic database integration (AFIS, CODIS)
- Multilingual report analysis
- Real-time IoT sensor feeds
- Federated learning for cross-agency collaboration
---
*Version 1.0 โ Demonstration of AI-assisted forensic investigation*
""")
gr.HTML("""<div style="text-align: center; padding: 20px; color: #8b949e; font-size: 0.85em; border-top: 1px solid #30363d; margin-top: 20px;">
๐ฌ Forensic Triage Intelligence System v1.0 โข Investigative Assistance Only โข Built with ๐ค Hugging Face & Gradio
</div>""")
return demo
if __name__ == "__main__":
demo = create_app()
demo.queue().launch(server_name="0.0.0.0", server_port=7860)
|