Migjomatic commited on
Commit
7995d4a
·
1 Parent(s): 8a74c03

Update project incl. ontology evaluation + triples test

Browse files
Files changed (2) hide show
  1. ontology_eval.py +227 -0
  2. test_ontology_triples.py +31 -0
ontology_eval.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ontology_eval.py
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum, IntEnum
5
+ from typing import Dict, List, Optional, Tuple
6
+ from datetime import datetime
7
+
8
+ # ============================================================================
9
+ # ONTOLOGIE-ANBINDUNG (an die in deiner Grafik gezeigten Klassen/Properties)
10
+ # --------------------------------------------------------------------------
11
+ # Klassen (Auszug): ex:Person, ex:Gleis, ex:Bahnsteig, ex:Zug, ex:Gefahr,
12
+ # ex:Videoüberwachung, ex:Sensor, ex:Alarmsystem, ex:Maßnahme
13
+ # Objekt-Properties: ex:befindetSichIn, ex:erkennt, ex:stehtAuf, ex:löstAus,
14
+ # ex:überwacht, ex:beobachtet, ex:meldet, ex:führtZu
15
+ # Daten-Properties : ex:hatKonfidenz (xsd:float), ex:hatZeitstempel (xsd:dateTime),
16
+ # ex:hatPosition (xsd:string), ex:hatBeschreibung (xsd:string)
17
+ # ============================================================================
18
+
19
+ EX = "ex:" # einfacher Prefix (du kannst z.B. "http://example.org/rail#" verwenden)
20
+
21
+ class Severity(IntEnum):
22
+ NONE = 0
23
+ LOW = 1
24
+ MEDIUM = 2
25
+ HIGH = 3
26
+ CRITICAL = 4
27
+
28
+ class HazardLabel(str, Enum):
29
+ PERSON_ON_TRACK = "PersonOnTrack" # ex:Person befindetSichIn ex:Gleis
30
+ NEAR_EDGE_TRAIN = "NearEdgeWithTrain" # ex:Person stehtAuf ex:BahnsteigKante ∧ ex:Zug in Szene
31
+ FALLEN_PERSON = "FallenPersonNearTrack" # ex:Person liegt/gestürzt nahe Gleis
32
+ OBJECT_ON_TRACK = "ObjectOnTrack" # ex:Objekt befindetSichIn ex:Gleis
33
+ SMOKE_FIRE = "SmokeOrFire" # ex:Rauch/Feuer als Gefahr
34
+ CROWD_OVERFLOW = "CrowdOverflowOnTrack" # ex:Menschenmenge im Gleisbereich
35
+
36
+ @dataclass
37
+ class Observation:
38
+ """Beobachtungen/Signale für eine Szene (alle Werte ∈ [0,1] sind Konfidenzen)."""
39
+ # Kontext-Geometrie
40
+ distance_to_edge_m: Optional[float] = None
41
+ train_approaching: float = 0.0
42
+ # Detektor-Konfidenzen
43
+ on_track_person: float = 0.0
44
+ fallen_person: float = 0.0
45
+ object_on_track: float = 0.0
46
+ smoke_or_fire: float = 0.0
47
+ crowd_on_track: float = 0.0
48
+ # Bias (Recall-Priorisierung)
49
+ class_threshold_recall_bias: float = 0.35
50
+ # Zusatzinfos
51
+ notes: Dict[str, float] = field(default_factory=dict)
52
+
53
+ @dataclass
54
+ class HazardDecision:
55
+ severity: Severity
56
+ score_0_100: int
57
+ labels: List[HazardLabel]
58
+ explanations: List[str]
59
+ fired_rules: List[str]
60
+
61
+ # --------------------------- REGELWERK ---------------------------------------
62
+
63
+ def _passes(p: float, thr: float) -> bool:
64
+ return p >= thr
65
+
66
+ def evaluate(ob: Observation) -> HazardDecision:
67
+ """Regelbasierte Bewertung mit erklärbarer Ausgabe (Ontologie-gedacht)."""
68
+ thr = ob.class_threshold_recall_bias
69
+ labels: List[HazardLabel] = []
70
+ explains: List[str] = []
71
+ fired: List[str] = []
72
+ score_terms: List[Tuple[Severity, float]] = []
73
+
74
+ # R1 — ex:Person ex:befindetSichIn ex:Gleis → Kritisch
75
+ if _passes(ob.on_track_person, thr):
76
+ labels.append(HazardLabel.PERSON_ON_TRACK)
77
+ fired.append("R1_befindetSichIn_Gleis")
78
+ explains.append(f"R1: Person im Gleis erkannt (p={ob.on_track_person:.2f}).")
79
+ score_terms.append((Severity.CRITICAL, 0.85 + 0.15 * ob.on_track_person))
80
+
81
+ # R2 — Nahe Kante + Zug → Hoch
82
+ if (ob.distance_to_edge_m is not None and ob.distance_to_edge_m <= 0.5) and _passes(ob.train_approaching, thr):
83
+ labels.append(HazardLabel.NEAR_EDGE_TRAIN)
84
+ fired.append("R2_stehtAuf_Bahnsteigkante_und_Zug")
85
+ explains.append(
86
+ f"R2: ≤0.5 m zur Kante (d={ob.distance_to_edge_m:.2f} m) & Zug (p={ob.train_approaching:.2f})."
87
+ )
88
+ score_terms.append((Severity.HIGH, 0.75 + 0.25 * ob.train_approaching))
89
+
90
+ # R3 — Gestürzte Person nahe Kante/auf Gleis → Hoch/Kritisch
91
+ if _passes(ob.fallen_person, thr):
92
+ if (ob.distance_to_edge_m is not None and ob.distance_to_edge_m <= 1.0) or _passes(ob.on_track_person, thr):
93
+ labels.append(HazardLabel.FALLEN_PERSON)
94
+ fired.append("R3_fallenPerson_in_Gefahrenzone")
95
+ explains.append(f"R3: Gestürzte Person (p={ob.fallen_person:.2f}).")
96
+ sev = Severity.CRITICAL if _passes(ob.on_track_person, thr) else Severity.HIGH
97
+ base = 0.80 if sev is Severity.CRITICAL else 0.70
98
+ score_terms.append((sev, base + 0.20 * ob.fallen_person))
99
+
100
+ # R4 — Objekt im Gleis → Mittel
101
+ if _passes(ob.object_on_track, thr):
102
+ labels.append(HazardLabel.OBJECT_ON_TRACK)
103
+ fired.append("R4_Objekt_im_Gleis")
104
+ explains.append(f"R4: Objekt im Gleis (p={ob.object_on_track:.2f}).")
105
+ score_terms.append((Severity.MEDIUM, 0.60 + 0.30 * ob.object_on_track))
106
+
107
+ # R5 — Rauch/Feuer → Hoch
108
+ if _passes(ob.smoke_or_fire, thr):
109
+ labels.append(HazardLabel.SMOKE_FIRE)
110
+ fired.append("R5_Rauch_oder_Feuer")
111
+ explains.append(f"R5: Rauch/Feuer (p={ob.smoke_or_fire:.2f}).")
112
+ score_terms.append((Severity.HIGH, 0.70 + 0.25 * ob.smoke_or_fire))
113
+
114
+ # R6 — Menschenmenge im Gleis → Kritisch
115
+ if _passes(ob.crowd_on_track, thr):
116
+ labels.append(HazardLabel.CROWD_OVERFLOW)
117
+ fired.append("R6_Menschenmenge_im_Gleisbereich")
118
+ explains.append(f"R6: Crowd im Gleis (p={ob.crowd_on_track:.2f}).")
119
+ score_terms.append((Severity.CRITICAL, 0.80 + 0.20 * ob.crowd_on_track))
120
+
121
+ if not score_terms:
122
+ return HazardDecision(
123
+ severity=Severity.NONE,
124
+ score_0_100=0,
125
+ labels=[],
126
+ explanations=["Keine Gefahrenrelation erfüllt."],
127
+ fired_rules=[]
128
+ )
129
+
130
+ sev_weights = {Severity.NONE:0.0, Severity.LOW:0.25, Severity.MEDIUM:0.55, Severity.HIGH:0.80, Severity.CRITICAL:1.0}
131
+ best = max(score_terms, key=lambda t: sev_weights[t[0]] * t[1])
132
+ best_sev, best_p = best
133
+ final_score = int(round(100 * sev_weights[best_sev] * best_p))
134
+
135
+ labels = list(dict.fromkeys(labels))
136
+ return HazardDecision(best_sev, final_score, labels, explains, fired)
137
+
138
+ # --------------------------- RDF/TRIPLES -------------------------------------
139
+ @dataclass
140
+ class OntologyContext:
141
+ """IDs/Metadaten für Tripel (du kannst echte IRIs verwenden)."""
142
+ person_id: str = "person1"
143
+ sensor_id: str = "sensor1"
144
+ video_system_id: str = "videoSys1"
145
+ track_id: str = "gleis1"
146
+ platform_id: str = "bahnsteig1"
147
+ alarm_id: str = "alarm1"
148
+ measure_id: str = "massnahme1"
149
+ event_id: str = "event1"
150
+ timestamp: datetime = field(default_factory=datetime.utcnow)
151
+ position: Optional[str] = None # z.B. "x=123,y=45,cam=2"
152
+
153
+ def _lit(value: str, dtype: str) -> str:
154
+ # Turtle-ähnlicher Literal-Renderer
155
+ return f"\"{value}\"^^{dtype}"
156
+
157
+ def decision_to_triples(dec: HazardDecision, ob: Observation, ctx: OntologyContext) -> List[Tuple[str,str,str]]:
158
+ """
159
+ Erzeugt RDF-ähnliche Tripel basierend auf der Ontologie aus deiner Grafik.
160
+ Nur stdlib; Ausgabe als einfache (s, p, o)-Tupel (Turtle-artig).
161
+ """
162
+ triples: List[Tuple[str,str,str]] = []
163
+
164
+ # Typisierungen (rdf:type)
165
+ triples += [
166
+ (EX+ctx.person_id, "rdf:type", EX+"Person"),
167
+ (EX+ctx.sensor_id, "rdf:type", EX+"Sensor"),
168
+ (EX+ctx.video_system_id, "rdf:type", EX+"Videoüberwachung"),
169
+ (EX+ctx.track_id, "rdf:type", EX+"Gleis"),
170
+ (EX+ctx.platform_id, "rdf:type", EX+"Bahnsteig"),
171
+ (EX+ctx.alarm_id, "rdf:type", EX+"Alarmsystem"),
172
+ (EX+ctx.measure_id, "rdf:type", EX+"Maßnahme"),
173
+ (EX+ctx.event_id, "rdf:type", EX+"Ereignis"),
174
+ (EX+"gef1", "rdf:type", EX+"Gefahr"),
175
+ ]
176
+
177
+ # Überwachung/Beobachtungskette
178
+ triples += [
179
+ (EX+ctx.video_system_id, EX+"überwacht", EX+ctx.platform_id),
180
+ (EX+ctx.sensor_id, EX+"beobachtet", EX+ctx.platform_id),
181
+ (EX+ctx.sensor_id, EX+"erkennt", EX+ctx.person_id),
182
+ ]
183
+
184
+ # Daten-Properties
185
+ triples.append((EX+ctx.event_id, EX+"hatZeitstempel", _lit(ctx.timestamp.isoformat(), "xsd:dateTime")))
186
+ if ctx.position:
187
+ triples.append((EX+ctx.person_id, EX+"hatPosition", _lit(ctx.position, "xsd:string")))
188
+
189
+ # Konfidenzen (nur wenn gesetzt)
190
+ def add_conf(name: str, val: float):
191
+ triples.append((EX+name, EX+"hatKonfidenz", _lit(f"{val:.3f}", "xsd:float")))
192
+
193
+ if ob.on_track_person: add_conf(ctx.person_id, ob.on_track_person)
194
+ if ob.object_on_track: triples.append((EX+"obj1", "rdf:type", EX+"Objekt")) or add_conf("obj1", ob.object_on_track)
195
+ if ob.smoke_or_fire: triples.append((EX+"smk1", "rdf:type", EX+"Unfall")) or add_conf("smk1", ob.smoke_or_fire)
196
+
197
+ # Ontologische Kernaussagen je nach Label
198
+ for lab in dec.labels:
199
+ if lab == HazardLabel.PERSON_ON_TRACK:
200
+ triples.append((EX+ctx.person_id, EX+"befindetSichIn", EX+ctx.track_id))
201
+ elif lab == HazardLabel.NEAR_EDGE_TRAIN:
202
+ # approximiert: Person steht (nahe) auf Bahnsteigkante
203
+ triples.append((EX+ctx.person_id, EX+"stehtAuf", EX+ctx.platform_id))
204
+ elif lab == HazardLabel.OBJECT_ON_TRACK:
205
+ triples.append((EX+"obj1", EX+"befindetSichIn", EX+ctx.track_id))
206
+ elif lab == HazardLabel.CROWD_OVERFLOW:
207
+ triples.append((EX+ctx.platform_id, EX+"istZugaenglich", _lit("false", "xsd:boolean")))
208
+
209
+ # Gefahr → löstAus → Alarm; Alarm → führtZu → Maßnahme
210
+ triples += [
211
+ (EX+"gef1", EX+"hatBeschreibung", _lit(f"Severity={dec.severity.name}; Score={dec.score_0_100}", "xsd:string")),
212
+ (EX+"gef1", EX+"löstAus", EX+ctx.alarm_id),
213
+ (EX+ctx.alarm_id, EX+"führtZu", EX+ctx.measure_id),
214
+ (EX+ctx.alarm_id, EX+"meldet", EX+"Polizei"), # optionaler Meldeweg
215
+ ]
216
+
217
+ return triples
218
+
219
+ def triples_to_turtle(triples: List[Tuple[str,str,str]]) -> str:
220
+ """Kleine Pretty-Printer-Hilfe für Logs/Datei-Export."""
221
+ lines = []
222
+ for s,p,o in triples:
223
+ if not o.startswith(EX) and not o.startswith("\""):
224
+ # Literale sind schon getaggt; ansonsten als Ressourcen belassen
225
+ o = o
226
+ lines.append(f"{s} {p} {o} .")
227
+ return "\n".join(lines)
test_ontology_triples.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_ontology_triples.py
2
+ from ontology_eval import Observation, evaluate, OntologyContext, decision_to_triples, triples_to_turtle
3
+
4
+ if __name__ == "__main__":
5
+ # Szenario: Person im Gleis + Zug naht -> kritisch
6
+ ob = Observation(
7
+ on_track_person=0.88,
8
+ train_approaching=0.9,
9
+ distance_to_edge_m=0.3
10
+ )
11
+
12
+ dec = evaluate(ob)
13
+ print(f"SEVERITY: {dec.severity.name} SCORE: {dec.score_0_100}")
14
+ for e in dec.explanations:
15
+ print(" -", e)
16
+
17
+ ctx = OntologyContext(
18
+ person_id="person42",
19
+ sensor_id="cam02",
20
+ video_system_id="videosysA",
21
+ track_id="gleis_3",
22
+ platform_id="bahnsteig_3",
23
+ alarm_id="alarm_4711",
24
+ measure_id="massnahme_stop",
25
+ event_id="event_2023_001",
26
+ position="x=123.4,y=56.7,cam=2"
27
+ )
28
+
29
+ triples = decision_to_triples(dec, ob, ctx)
30
+ print("\n--- Turtle ---")
31
+ print(triples_to_turtle(triples))