Muthukumarank commited on
Commit
e36bc3e
ยท
verified ยท
1 Parent(s): 379b70a

Add app.py

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