fluxstate-security 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fluxstate_security/__init__.py +3 -0
- fluxstate_security/app.py +467 -0
- fluxstate_security/core/__init__.py +1 -0
- fluxstate_security/core/agent.py +108 -0
- fluxstate_security/core/engine.py +98 -0
- fluxstate_security/core/forensics.py +80 -0
- fluxstate_security/core/state_manager.py +39 -0
- fluxstate_security/intelligence_policy.json +29 -0
- fluxstate_security/utils/__init__.py +1 -0
- fluxstate_security/utils/video_stream.py +363 -0
- fluxstate_security-1.0.0.dist-info/METADATA +162 -0
- fluxstate_security-1.0.0.dist-info/RECORD +14 -0
- fluxstate_security-1.0.0.dist-info/WHEEL +5 -0
- fluxstate_security-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# app.py
|
|
2
|
+
import time
|
|
3
|
+
import sys
|
|
4
|
+
import cv2
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
import requests
|
|
8
|
+
import ctypes
|
|
9
|
+
import numpy as np
|
|
10
|
+
import datetime
|
|
11
|
+
from collections import defaultdict, deque
|
|
12
|
+
from .utils.video_stream import EdgeVideoStream
|
|
13
|
+
from .core.state_manager import RealityGraph, IntelligenceState
|
|
14
|
+
from .core.engine import FluxInferenceEngine
|
|
15
|
+
from .core.forensics import ForensicDatabase
|
|
16
|
+
from .core.agent import SemanticAgent
|
|
17
|
+
|
|
18
|
+
class PhysicsTracker:
|
|
19
|
+
def __init__(self, history_frames=10):
|
|
20
|
+
# Maps a ByteTrack ID to a rolling queue of their last 10 coordinates
|
|
21
|
+
self.history = defaultdict(lambda: deque(maxlen=history_frames))
|
|
22
|
+
|
|
23
|
+
def get_smoothed_vector(self, track_id, current_centroid):
|
|
24
|
+
self.history[track_id].append(current_centroid)
|
|
25
|
+
pts = list(self.history[track_id])
|
|
26
|
+
|
|
27
|
+
# Wait for enough frames to calculate stable momentum
|
|
28
|
+
if len(pts) < 3:
|
|
29
|
+
return (0, 0)
|
|
30
|
+
|
|
31
|
+
# Calculate rolling average of velocity (dx, dy)
|
|
32
|
+
dx = np.mean([pts[i][0] - pts[i-1][0] for i in range(1, len(pts))])
|
|
33
|
+
dy = np.mean([pts[i][1] - pts[i-1][1] for i in range(1, len(pts))])
|
|
34
|
+
|
|
35
|
+
# Multiply by a scalar to predict the future position robustly
|
|
36
|
+
return (dx * 10, dy * 10)
|
|
37
|
+
|
|
38
|
+
class FluxStateNode:
|
|
39
|
+
"""
|
|
40
|
+
The core FluxState Edge Library.
|
|
41
|
+
Can be deployed headlessly on existing NVR/CCTV networks to emit JSON telemetry,
|
|
42
|
+
or run locally with the Holographic UI for debugging.
|
|
43
|
+
"""
|
|
44
|
+
def __init__(self, stream_source=None):
|
|
45
|
+
import json
|
|
46
|
+
try:
|
|
47
|
+
import os
|
|
48
|
+
policy_path = os.path.join(os.path.dirname(__file__), "intelligence_policy.json")
|
|
49
|
+
with open(policy_path, "r") as f:
|
|
50
|
+
self.policy = json.load(f)
|
|
51
|
+
except:
|
|
52
|
+
self.policy = {}
|
|
53
|
+
|
|
54
|
+
if stream_source is None:
|
|
55
|
+
# Fallback to policy source (RTSP IP Camera string or local USB int 0)
|
|
56
|
+
ingestion = self.policy.get("INGESTION", {})
|
|
57
|
+
stream_source = ingestion.get("rtsp_stream_url") if ingestion.get("rtsp_stream_url") else ingestion.get("source", 0)
|
|
58
|
+
# If string is empty, default to 0
|
|
59
|
+
if stream_source == "": stream_source = 0
|
|
60
|
+
|
|
61
|
+
self.stream = EdgeVideoStream(stream_source)
|
|
62
|
+
self.graph = RealityGraph()
|
|
63
|
+
self.ai_engine = FluxInferenceEngine()
|
|
64
|
+
self.physics_tracker = PhysicsTracker()
|
|
65
|
+
self.forensics = ForensicDatabase()
|
|
66
|
+
self.agent = SemanticAgent()
|
|
67
|
+
|
|
68
|
+
# Seamless SDK Integration Hooks
|
|
69
|
+
self.on_threat_detected = None
|
|
70
|
+
self.on_telemetry_update = None
|
|
71
|
+
self.is_running = True
|
|
72
|
+
|
|
73
|
+
def _push_to_integration_bus(self, telemetry):
|
|
74
|
+
"""
|
|
75
|
+
Commercial VMS Integration: Pushes JSON telemetry to central dashboards via Webhooks.
|
|
76
|
+
"""
|
|
77
|
+
import urllib.request
|
|
78
|
+
import json
|
|
79
|
+
import threading
|
|
80
|
+
|
|
81
|
+
bus_config = self.policy.get("INTEGRATION_BUS", {})
|
|
82
|
+
webhook_url = bus_config.get("webhook_url")
|
|
83
|
+
if not webhook_url: return
|
|
84
|
+
|
|
85
|
+
push_on_threat = bus_config.get("push_on_threat_only", True)
|
|
86
|
+
if push_on_threat:
|
|
87
|
+
context = telemetry.get("context_log", "")
|
|
88
|
+
if "THREAT VECTOR" not in context:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
def _post():
|
|
92
|
+
try:
|
|
93
|
+
req = urllib.request.Request(webhook_url, data=json.dumps(telemetry).encode('utf-8'), headers={'Content-Type': 'application/json'})
|
|
94
|
+
urllib.request.urlopen(req, timeout=2)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass # Fail silently to prevent edge node crash if central server goes down
|
|
97
|
+
|
|
98
|
+
threading.Thread(target=_post, daemon=True).start()
|
|
99
|
+
|
|
100
|
+
def poll_telemetry(self):
|
|
101
|
+
"""
|
|
102
|
+
Headless API: Runs one tick of the edge inference pipeline and returns the semantic JSON telemetry.
|
|
103
|
+
This is the actual production hook used to send data to the Web Command Center.
|
|
104
|
+
"""
|
|
105
|
+
should_infer, frame, movement_boxes, detected_objects, pose_landmarks, action, rf_count, acoustic = self.stream.capture_and_filter()
|
|
106
|
+
|
|
107
|
+
telemetry = {
|
|
108
|
+
"timestamp": time.time(),
|
|
109
|
+
"should_infer": should_infer,
|
|
110
|
+
"rf_devices_nearby": rf_count,
|
|
111
|
+
"acoustic_event": acoustic,
|
|
112
|
+
"entities": [],
|
|
113
|
+
"action_heuristic": action
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (x, y, bw, bh, label, conf, obj_id) in detected_objects:
|
|
117
|
+
cx, cy = x + bw // 2, y + bh // 2
|
|
118
|
+
dx, dy = self.physics_tracker.get_smoothed_vector(obj_id, (cx, cy))
|
|
119
|
+
telemetry["entities"].append({
|
|
120
|
+
"id": obj_id,
|
|
121
|
+
"label": label,
|
|
122
|
+
"confidence": conf,
|
|
123
|
+
"position_2d": {"x": cx, "y": cy},
|
|
124
|
+
"velocity_vector": {"dx": dx, "dy": dy}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
if should_infer:
|
|
128
|
+
context = self.ai_engine.generate_context_reasoning(detected_objects, movement_boxes, action, rf_count, acoustic)
|
|
129
|
+
self.graph.log_event(context)
|
|
130
|
+
telemetry["context_log"] = context
|
|
131
|
+
|
|
132
|
+
# --- VLM AGENT INTERCEPTION ---
|
|
133
|
+
if "THREAT VECTOR" in context or "ANOMALY" in context.upper():
|
|
134
|
+
# Annotate frame to give the VLM explicit visual targeting
|
|
135
|
+
vlm_frame = frame.copy()
|
|
136
|
+
for (x, y, bw, bh, label, conf, obj_id) in detected_objects:
|
|
137
|
+
if "person" not in label.lower():
|
|
138
|
+
cv2.rectangle(vlm_frame, (x, y), (x + bw, y + bh), (0, 0, 255), 3)
|
|
139
|
+
cv2.putText(vlm_frame, "TARGET ANOMALY", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
|
|
140
|
+
|
|
141
|
+
prompt = (
|
|
142
|
+
"Initiate Priority Threat Assessment. FOCUS YOUR ATTENTION EXCLUSIVELY ON THE OBJECT(S) OUTLINED IN RED BOUNDING BOXES. "
|
|
143
|
+
"Perform a high-fidelity visual inspection of the pixels inside the red boundaries. "
|
|
144
|
+
"Determine if the bounded object represents a tactical threat, such as an improvised weapon, firearm, explosive, or hostile instrument. "
|
|
145
|
+
"Rule out harmless civilian electronics or everyday objects based on visual evidence. "
|
|
146
|
+
"Provide a definitive classification of the anomaly and assess the immediate tactical risk."
|
|
147
|
+
)
|
|
148
|
+
vlm_reasoning = self.agent.investigate_scene(context, vlm_frame, prompt=prompt)
|
|
149
|
+
telemetry["vlm_reasoning"] = vlm_reasoning
|
|
150
|
+
print(f"[VLM Reasoner] {vlm_reasoning}")
|
|
151
|
+
|
|
152
|
+
self._push_to_integration_bus(telemetry)
|
|
153
|
+
self.forensics.log_event(telemetry)
|
|
154
|
+
|
|
155
|
+
# Fire SDK Hooks
|
|
156
|
+
if self.on_threat_detected and "THREAT VECTOR" in context:
|
|
157
|
+
self.on_threat_detected(telemetry)
|
|
158
|
+
|
|
159
|
+
if self.on_telemetry_update:
|
|
160
|
+
self.on_telemetry_update(telemetry)
|
|
161
|
+
|
|
162
|
+
# --- TRUE ZERO-TRACE PRIVACY ENGINE (C-Level Wipe) ---
|
|
163
|
+
# Honest Engineering: Python GC is unsafe for privacy.
|
|
164
|
+
# We extract the underlying C memory pointer of the numpy array
|
|
165
|
+
# and forcefully memset it to 0 before the frame is destroyed.
|
|
166
|
+
if frame is not None:
|
|
167
|
+
ctypes.memset(frame.ctypes.data, 0, frame.nbytes)
|
|
168
|
+
|
|
169
|
+
return telemetry
|
|
170
|
+
|
|
171
|
+
def start_headless_daemon(self):
|
|
172
|
+
"""
|
|
173
|
+
Production SDK Entrypoint: Spawns the inference engine in a non-blocking daemon thread.
|
|
174
|
+
Fires user-defined callbacks silently in the background.
|
|
175
|
+
"""
|
|
176
|
+
import threading
|
|
177
|
+
print("[SDK] Initializing Zero-Trace Headless Daemon...")
|
|
178
|
+
|
|
179
|
+
def _loop():
|
|
180
|
+
while self.is_running:
|
|
181
|
+
try:
|
|
182
|
+
self.poll_telemetry()
|
|
183
|
+
time.sleep(0.01)
|
|
184
|
+
except RuntimeError as e:
|
|
185
|
+
if "shutdown" in str(e).lower():
|
|
186
|
+
break # Prevent ThreadPoolExecutor crash on Ctrl+C exit
|
|
187
|
+
raise e
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
threading.Thread(target=_loop, daemon=True).start()
|
|
192
|
+
|
|
193
|
+
def stop(self):
|
|
194
|
+
"""
|
|
195
|
+
Cleanly terminates the SDK and releases hardware resources.
|
|
196
|
+
"""
|
|
197
|
+
self.is_running = False
|
|
198
|
+
try:
|
|
199
|
+
self.stream.close()
|
|
200
|
+
except:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
def run_debug_ui(self):
|
|
204
|
+
"""
|
|
205
|
+
Runs the full 3D Holographic MVP UI on the local machine for demonstration purposes.
|
|
206
|
+
"""
|
|
207
|
+
print(f"==================================================")
|
|
208
|
+
print(f" Starting FluxState Edge Node (Debug UI) ")
|
|
209
|
+
print(f"==================================================")
|
|
210
|
+
try:
|
|
211
|
+
while True:
|
|
212
|
+
should_infer, frame, movement_boxes, detected_objects, pose_landmarks, action_heuristic, rf_count, acoustic = self.stream.capture_and_filter()
|
|
213
|
+
if frame is None:
|
|
214
|
+
time.sleep(5)
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
ui_frame = frame.copy()
|
|
218
|
+
h, w = ui_frame.shape[:2]
|
|
219
|
+
|
|
220
|
+
overlay = ui_frame.copy()
|
|
221
|
+
cv2.rectangle(overlay, (0, 0), (350, h), (10, 15, 20), -1)
|
|
222
|
+
cv2.rectangle(overlay, (w - 300, 0), (w, h), (10, 15, 20), -1)
|
|
223
|
+
cv2.addWeighted(overlay, 0.7, ui_frame, 0.3, 0, ui_frame)
|
|
224
|
+
|
|
225
|
+
cv2.putText(ui_frame, "FLUXSTATE REALITY GRAPH", (15, 30), cv2.FONT_HERSHEY_TRIPLEX, 0.6, (0, 255, 255), 1)
|
|
226
|
+
cv2.putText(ui_frame, f"TIME: {datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]}", (15, 55), cv2.FONT_HERSHEY_PLAIN, 1.0, (200, 200, 200), 1)
|
|
227
|
+
|
|
228
|
+
cv2.putText(ui_frame, "SEMANTIC TELEMETRY:", (15, 90), cv2.FONT_HERSHEY_PLAIN, 1.0, (0, 200, 0), 1)
|
|
229
|
+
|
|
230
|
+
persons = [obj for obj in detected_objects if "person" in obj[4].lower()]
|
|
231
|
+
cv2.putText(ui_frame, f"Human Entities: {len(persons)}", (15, 110), cv2.FONT_HERSHEY_PLAIN, 0.9, (255, 0, 255), 1)
|
|
232
|
+
cv2.putText(ui_frame, f"Object Entities: {len(detected_objects) - len(persons)}", (15, 130), cv2.FONT_HERSHEY_PLAIN, 0.9, (0, 255, 255), 1)
|
|
233
|
+
cv2.putText(ui_frame, f"Kinetic Deltas: {len(movement_boxes)}", (15, 150), cv2.FONT_HERSHEY_PLAIN, 0.9, (255, 100, 0), 1)
|
|
234
|
+
cv2.putText(ui_frame, f"Primary Action: {action_heuristic}", (15, 170), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 255, 0), 1)
|
|
235
|
+
cv2.putText(ui_frame, f"RF MAC/IP Devices: {rf_count}", (15, 190), cv2.FONT_HERSHEY_PLAIN, 0.8, (255, 255, 0), 1)
|
|
236
|
+
cv2.putText(ui_frame, f"Acoustic State: {acoustic}", (15, 210), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 100, 255), 1)
|
|
237
|
+
|
|
238
|
+
# --- 3D ISOMETRIC DIGITAL TWIN INITIALIZATION ---
|
|
239
|
+
minimap_size = 250
|
|
240
|
+
flat_map = np.zeros((minimap_size, minimap_size, 3), dtype=np.uint8)
|
|
241
|
+
|
|
242
|
+
# Draw Hologram Grid (Glowing Cyan/Teal) on 2D floor
|
|
243
|
+
grid_color = (25, 45, 25)
|
|
244
|
+
for i in range(0, minimap_size, 25):
|
|
245
|
+
cv2.line(flat_map, (i, 0), (i, minimap_size), grid_color, 1)
|
|
246
|
+
cv2.line(flat_map, (0, i), (minimap_size, i), grid_color, 1)
|
|
247
|
+
|
|
248
|
+
# Draw a simulated restricted Geofenced "Red Zone"
|
|
249
|
+
cv2.rectangle(flat_map, (150, 150), (230, 230), (0, 0, 50), -1)
|
|
250
|
+
cv2.rectangle(flat_map, (150, 150), (230, 230), (0, 0, 255), 1)
|
|
251
|
+
|
|
252
|
+
entities_to_draw = []
|
|
253
|
+
|
|
254
|
+
# --- CENTRAL CAMERA VIEW & TRAJECTORIES ---
|
|
255
|
+
for (x, y, bw, bh) in movement_boxes:
|
|
256
|
+
cv2.rectangle(ui_frame, (x, y), (x + bw, y + bh), (255, 100, 0), 1)
|
|
257
|
+
cv2.drawMarker(ui_frame, (x + bw//2, y + bh//2), (255, 100, 0), cv2.MARKER_CROSS, 10, 1)
|
|
258
|
+
|
|
259
|
+
for (x, y, bw, bh, label, conf, obj_id) in detected_objects:
|
|
260
|
+
box_color = (255, 0, 255) if "person" in label.lower() else (0, 255, 255)
|
|
261
|
+
cv2.rectangle(ui_frame, (x, y), (x + bw, y + bh), box_color, 2)
|
|
262
|
+
cv2.putText(ui_frame, f"[{label.upper()}] {conf:.2f}", (x, y - 5), cv2.FONT_HERSHEY_PLAIN, 0.8, box_color, 1)
|
|
263
|
+
|
|
264
|
+
# 2. Physics & Trajectory Engine (Anchored to Feet)
|
|
265
|
+
foot_x = x + bw // 2
|
|
266
|
+
foot_y = y + bh
|
|
267
|
+
|
|
268
|
+
dx, dy = self.physics_tracker.get_smoothed_vector(obj_id, (foot_x, foot_y))
|
|
269
|
+
|
|
270
|
+
if abs(dx) > 2 or abs(dy) > 2:
|
|
271
|
+
pred_x = int(foot_x + dx)
|
|
272
|
+
pred_y = int(foot_y + dy)
|
|
273
|
+
cv2.arrowedLine(ui_frame, (foot_x, foot_y), (pred_x, pred_y), (0, 0, 255), 2, tipLength=0.2)
|
|
274
|
+
|
|
275
|
+
# 3. Queue entities for 3D mapping
|
|
276
|
+
map_x = int((foot_x / w) * minimap_size)
|
|
277
|
+
map_y = int((foot_y / h) * minimap_size)
|
|
278
|
+
|
|
279
|
+
pred_map_x = int(map_x + (dx / w) * minimap_size)
|
|
280
|
+
pred_map_y = int(map_y + (dy / h) * minimap_size)
|
|
281
|
+
|
|
282
|
+
foot_color = (0, 100, 50) if "person" in label.lower() else (0, 70, 120)
|
|
283
|
+
cv2.circle(flat_map, (map_x, map_y), 6, foot_color, -1)
|
|
284
|
+
if abs(dx) > 2 or abs(dy) > 2:
|
|
285
|
+
cv2.line(flat_map, (map_x, map_y), (pred_map_x, pred_map_y), (0, 0, 150), 1)
|
|
286
|
+
|
|
287
|
+
physical_height = int((bh / h) * minimap_size)
|
|
288
|
+
entities_to_draw.append((map_x, map_y, label, physical_height, pred_map_x, pred_map_y))
|
|
289
|
+
|
|
290
|
+
# --- WARP 2D FLOORPLAN INTO 3D ISOMETRIC PERSPECTIVE ---
|
|
291
|
+
src_pts = np.float32([[0, 0], [minimap_size, 0], [0, minimap_size], [minimap_size, minimap_size]])
|
|
292
|
+
trap_top_width = int(minimap_size * 0.4)
|
|
293
|
+
trap_offset = (minimap_size - trap_top_width) // 2
|
|
294
|
+
|
|
295
|
+
dst_pts = np.float32([
|
|
296
|
+
[trap_offset, 60],
|
|
297
|
+
[trap_offset + trap_top_width, 60],
|
|
298
|
+
[10, minimap_size - 10],
|
|
299
|
+
[minimap_size - 10, minimap_size - 10]
|
|
300
|
+
])
|
|
301
|
+
|
|
302
|
+
matrix = cv2.getPerspectiveTransform(src_pts, dst_pts)
|
|
303
|
+
iso_map = cv2.warpPerspective(flat_map, matrix, (minimap_size, minimap_size))
|
|
304
|
+
|
|
305
|
+
# --- PROJECT ENTITIES VERTICALLY INTO 3D SPACE ---
|
|
306
|
+
if len(entities_to_draw) > 0:
|
|
307
|
+
pts_2d = np.float32([[[e[0], e[1]]] for e in entities_to_draw])
|
|
308
|
+
pts_3d_floor = cv2.perspectiveTransform(pts_2d, matrix)
|
|
309
|
+
|
|
310
|
+
for i, (map_x, map_y, label, e_h, pred_x, pred_y) in enumerate(entities_to_draw):
|
|
311
|
+
floor_px = int(pts_3d_floor[i][0][0])
|
|
312
|
+
floor_py = int(pts_3d_floor[i][0][1])
|
|
313
|
+
|
|
314
|
+
if "person" in label.lower():
|
|
315
|
+
map_color = (0, 255, 150)
|
|
316
|
+
marker = cv2.MARKER_SQUARE
|
|
317
|
+
txt = "HUMAN"
|
|
318
|
+
else:
|
|
319
|
+
map_color = (0, 150, 255)
|
|
320
|
+
marker = cv2.MARKER_DIAMOND
|
|
321
|
+
txt = label.split()[0].upper()[:8]
|
|
322
|
+
|
|
323
|
+
top_px = floor_px
|
|
324
|
+
top_py = floor_py - int(e_h * 0.6)
|
|
325
|
+
|
|
326
|
+
cv2.line(iso_map, (floor_px, floor_py), (top_px, top_py), map_color, 2)
|
|
327
|
+
cv2.drawMarker(iso_map, (top_px, top_py), map_color, marker, 8, 2)
|
|
328
|
+
cv2.putText(iso_map, txt, (top_px + 10, top_py + 4), cv2.FONT_HERSHEY_PLAIN, 0.7, map_color, 1)
|
|
329
|
+
|
|
330
|
+
if 150 < pred_x < 230 and 150 < pred_y < 230:
|
|
331
|
+
cv2.putText(ui_frame, "PRE-BREACH INTENT DETECTED", (350, minimap_size + 30), cv2.FONT_HERSHEY_TRIPLEX, 0.6, (0, 0, 255), 1)
|
|
332
|
+
|
|
333
|
+
cv2.putText(iso_map, "3D ARCHITECTURAL TWIN", (5, 15), cv2.FONT_HERSHEY_PLAIN, 0.7, (0, 255, 200), 1)
|
|
334
|
+
|
|
335
|
+
roi = ui_frame[10:10+minimap_size, 370:370+minimap_size]
|
|
336
|
+
blended = cv2.addWeighted(roi, 0.2, iso_map, 0.9, 0)
|
|
337
|
+
ui_frame[10:10+minimap_size, 370:370+minimap_size] = blended
|
|
338
|
+
|
|
339
|
+
# --- 3D SKELETAL RENDERING ---
|
|
340
|
+
if pose_landmarks:
|
|
341
|
+
key_points = {
|
|
342
|
+
0: "NOSE",
|
|
343
|
+
11: "L_SHOULDER", 12: "R_SHOULDER",
|
|
344
|
+
13: "L_ELBOW", 14: "R_ELBOW",
|
|
345
|
+
15: "L_WRIST", 16: "R_WRIST",
|
|
346
|
+
23: "L_HIP", 24: "R_HIP"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
connections = [(11, 12), (11, 13), (13, 15), (12, 14), (14, 16), (11, 23), (12, 24), (23, 24)]
|
|
350
|
+
for start, end in connections:
|
|
351
|
+
if start < len(pose_landmarks) and end < len(pose_landmarks):
|
|
352
|
+
pt1 = pose_landmarks[start]
|
|
353
|
+
pt2 = pose_landmarks[end]
|
|
354
|
+
if pt1.visibility > 0.3 and pt2.visibility > 0.3:
|
|
355
|
+
x1, y1 = int(pt1.x * w), int(pt1.y * h)
|
|
356
|
+
x2, y2 = int(pt2.x * w), int(pt2.y * h)
|
|
357
|
+
cv2.line(ui_frame, (x1, y1), (x2, y2), (245, 117, 66), 2)
|
|
358
|
+
|
|
359
|
+
for idx, label in key_points.items():
|
|
360
|
+
if idx < len(pose_landmarks):
|
|
361
|
+
lm = pose_landmarks[idx]
|
|
362
|
+
if lm.visibility > 0.3:
|
|
363
|
+
cx, cy = int(lm.x * w), int(lm.y * h)
|
|
364
|
+
cv2.circle(ui_frame, (cx, cy), 4, (245, 66, 230), -1)
|
|
365
|
+
cv2.putText(ui_frame, f"{label} ({lm.visibility:.2f})", (cx + 5, cy - 5), cv2.FONT_HERSHEY_PLAIN, 0.7, (0, 255, 0), 1)
|
|
366
|
+
|
|
367
|
+
# --- DYNAMIC REASONING ENGINE ---
|
|
368
|
+
cv2.putText(ui_frame, "CONTEXTUAL EVENT LOG", (w - 285, 30), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0, 255, 255), 1)
|
|
369
|
+
|
|
370
|
+
context = ""
|
|
371
|
+
if not should_infer:
|
|
372
|
+
self.graph.set_state(IntelligenceState.IDLE)
|
|
373
|
+
if time.time() - self.graph.last_event_time > 10:
|
|
374
|
+
self.graph.log_event("Environment stable. No kinetic anomalies.")
|
|
375
|
+
else:
|
|
376
|
+
self.graph.set_state(IntelligenceState.REASONING)
|
|
377
|
+
context = self.ai_engine.generate_context_reasoning(detected_objects, movement_boxes, action_heuristic, rf_count, acoustic)
|
|
378
|
+
self.graph.log_event(context)
|
|
379
|
+
|
|
380
|
+
# Cache telemetry for the API Server
|
|
381
|
+
self.latest_telemetry = {
|
|
382
|
+
"timestamp": time.time(),
|
|
383
|
+
"should_infer": should_infer,
|
|
384
|
+
"rf_devices_nearby": rf_count,
|
|
385
|
+
"acoustic_event": acoustic,
|
|
386
|
+
"entities": [{"id": e[6], "label": e[4], "confidence": e[5]} for e in detected_objects],
|
|
387
|
+
"action_heuristic": action_heuristic,
|
|
388
|
+
"context_log": context
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
# Push active threats to Webhook VMS Integration
|
|
392
|
+
if should_infer:
|
|
393
|
+
self._push_to_integration_bus(self.latest_telemetry)
|
|
394
|
+
|
|
395
|
+
# Draw Event Log
|
|
396
|
+
y_offset = 70
|
|
397
|
+
for i, event in enumerate(reversed(self.graph.get_recent_events(6))):
|
|
398
|
+
text = event['summary']
|
|
399
|
+
words = text.split(' ')
|
|
400
|
+
lines = []
|
|
401
|
+
current_line = ""
|
|
402
|
+
for word in words:
|
|
403
|
+
if len(current_line) + len(word) < 30:
|
|
404
|
+
current_line += word + " "
|
|
405
|
+
else:
|
|
406
|
+
lines.append(current_line)
|
|
407
|
+
current_line = word + " "
|
|
408
|
+
lines.append(current_line)
|
|
409
|
+
|
|
410
|
+
cv2.putText(ui_frame, f"> {time.strftime('%H:%M:%S', time.localtime(event['timestamp']))}", (w - 285, y_offset), cv2.FONT_HERSHEY_PLAIN, 0.8, (0, 200, 0), 1)
|
|
411
|
+
for line in lines:
|
|
412
|
+
y_offset += 15
|
|
413
|
+
cv2.putText(ui_frame, line.strip(), (w - 275, y_offset), cv2.FONT_HERSHEY_PLAIN, 0.7, (200, 200, 200), 1)
|
|
414
|
+
y_offset += 20
|
|
415
|
+
|
|
416
|
+
# Bottom Status Bar
|
|
417
|
+
cv2.rectangle(ui_frame, (0, h - 30), (w, h), (30, 30, 30), -1)
|
|
418
|
+
cv2.putText(ui_frame, f"SYSTEM STATE: {self.graph.current_state.value}", (15, h - 10), cv2.FONT_HERSHEY_TRIPLEX, 0.6, (0, 255, 255), 1)
|
|
419
|
+
|
|
420
|
+
cv2.imshow("FluxState Edge Debug Node", ui_frame)
|
|
421
|
+
if cv2.waitKey(30) & 0xFF == ord('q'):
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
except KeyboardInterrupt:
|
|
425
|
+
pass
|
|
426
|
+
finally:
|
|
427
|
+
self.stream.close()
|
|
428
|
+
cv2.destroyAllWindows()
|
|
429
|
+
sys.exit(0)
|
|
430
|
+
|
|
431
|
+
def start_api_server(self, port=8000):
|
|
432
|
+
"""
|
|
433
|
+
Starts a background HTTP server on port 8000 to broadcast the Reality Graph JSON telemetry.
|
|
434
|
+
This allows any Command Center Web Dashboard to hook into this Edge Node.
|
|
435
|
+
"""
|
|
436
|
+
import json
|
|
437
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
438
|
+
import threading
|
|
439
|
+
|
|
440
|
+
node = self
|
|
441
|
+
|
|
442
|
+
class TelemetryHandler(BaseHTTPRequestHandler):
|
|
443
|
+
def do_GET(req):
|
|
444
|
+
if req.path == '/telemetry':
|
|
445
|
+
req.send_response(200)
|
|
446
|
+
req.send_header('Content-type', 'application/json')
|
|
447
|
+
req.send_header('Access-Control-Allow-Origin', '*')
|
|
448
|
+
req.end_headers()
|
|
449
|
+
|
|
450
|
+
# Fetch live telemetry (or cached if running UI concurrently)
|
|
451
|
+
data = getattr(node, 'latest_telemetry', node.poll_telemetry())
|
|
452
|
+
req.wfile.write(json.dumps(data).encode('utf-8'))
|
|
453
|
+
else:
|
|
454
|
+
req.send_response(404)
|
|
455
|
+
req.end_headers()
|
|
456
|
+
|
|
457
|
+
def log_message(self, format, *args):
|
|
458
|
+
pass # Suppress HTTP logs to keep terminal clean
|
|
459
|
+
|
|
460
|
+
server = HTTPServer(('0.0.0.0', port), TelemetryHandler)
|
|
461
|
+
print(f"[Network] FluxState Edge JSON API broadcasting on http://localhost:{port}/telemetry")
|
|
462
|
+
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
463
|
+
|
|
464
|
+
if __name__ == "__main__":
|
|
465
|
+
node = FluxStateNode()
|
|
466
|
+
node.start_api_server(port=8000)
|
|
467
|
+
node.run_debug_ui()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# core/__init__.py
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agentic VLM Reasoner (Vision-Language Model Orchestrator)
|
|
3
|
+
This module acts as the bridge between the lower-level FluxState tracking/detection
|
|
4
|
+
and higher-level semantic reasoning.
|
|
5
|
+
|
|
6
|
+
It intercepts complex temporal events and passes them to a Vision-Language Model
|
|
7
|
+
for deep contextual understanding.
|
|
8
|
+
|
|
9
|
+
V1.3.0 Update: Unified Memory Execution
|
|
10
|
+
This agent now leverages the MLX framework to execute VLMs (like Qwen2.5-VL)
|
|
11
|
+
directly on the Neural Engine and GPU of Apple Silicon hardware.
|
|
12
|
+
"""
|
|
13
|
+
import base64
|
|
14
|
+
import cv2
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from mlx_vlm import load, generate
|
|
18
|
+
MLX_AVAILABLE = True
|
|
19
|
+
except Exception as e:
|
|
20
|
+
MLX_AVAILABLE = False
|
|
21
|
+
print(f"[Warning] MLX VLM dependencies failed to load: {e}. SemanticAgent will fallback to mock/offline mode.")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SemanticAgent:
|
|
25
|
+
def __init__(self, model_path="qwen/Qwen2.5-VL-3B-Instruct"):
|
|
26
|
+
"""
|
|
27
|
+
Initializes the VLM locally on Apple Silicon Unified Memory using MLX.
|
|
28
|
+
Downloads the model from HuggingFace if not present.
|
|
29
|
+
"""
|
|
30
|
+
self.enabled = False
|
|
31
|
+
self.model = None
|
|
32
|
+
self.processor = None
|
|
33
|
+
self.config = None
|
|
34
|
+
|
|
35
|
+
if MLX_AVAILABLE:
|
|
36
|
+
try:
|
|
37
|
+
print(f"[VLM] Initializing {model_path} via MLX on Apple Silicon GPU...")
|
|
38
|
+
# In production, we load this asynchronously or as a singleton.
|
|
39
|
+
# For this release architecture, we scaffold the MLX loading:
|
|
40
|
+
self.model, self.processor = load(model_path, trust_remote_code=True)
|
|
41
|
+
self.enabled = True
|
|
42
|
+
print("[VLM] Model loaded into unified memory successfully.")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f"[VLM Error] Could not load MLX model: {e}")
|
|
45
|
+
|
|
46
|
+
def investigate_scene(self, context_log, frame, prompt="Initiate Visual Threat Analysis. Scan the optical feed for tactical anomalies. Classify any handheld objects and assess the subject's intent."):
|
|
47
|
+
"""
|
|
48
|
+
Takes a flagged event and a raw frame, and asks the local MLX VLM to reason about it.
|
|
49
|
+
This provides true 'Scene Understanding' beyond simple bounding boxes.
|
|
50
|
+
"""
|
|
51
|
+
if not self.enabled or frame is None:
|
|
52
|
+
return "[VLM Offline] Fallback to standard rule-engine."
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
# Encode frame to disk temporarily for MLX (or pass numpy array if supported)
|
|
56
|
+
temp_img_path = "/tmp/flux_vlm_frame.jpg"
|
|
57
|
+
cv2.imwrite(temp_img_path, frame)
|
|
58
|
+
|
|
59
|
+
# Construct the Qwen-VL specific prompt format
|
|
60
|
+
messages = [
|
|
61
|
+
{"role": "user", "content": [
|
|
62
|
+
{"type": "image"},
|
|
63
|
+
{"type": "text", "text": f"Context Log: {context_log}. Query: {prompt}"}
|
|
64
|
+
]}
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
formatted_prompt = self.processor.apply_chat_template(
|
|
68
|
+
messages, tokenize=False, add_generation_prompt=True
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Execute inference on the Apple Silicon GPU
|
|
72
|
+
response = generate(self.model, self.processor, formatted_prompt, [temp_img_path], verbose=False)
|
|
73
|
+
return response.text if hasattr(response, 'text') else str(response)
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
return f"[VLM Error] {str(e)}"
|
|
77
|
+
|
|
78
|
+
def query_temporal_memory(self, sql_results, natural_language_query):
|
|
79
|
+
"""
|
|
80
|
+
Allows an operator to ask: 'What happened between 2 PM and 3 PM?'
|
|
81
|
+
Takes the SQLite output and uses the local MLX VLM to generate a human-readable summary.
|
|
82
|
+
"""
|
|
83
|
+
if not self.enabled:
|
|
84
|
+
return "[VLM Offline] Temporal summary requires an active reasoning agent."
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Format the raw SQL forensics into a context block
|
|
88
|
+
context_block = "\n".join([f"[{row[0]}] {row[1]}" for row in sql_results])
|
|
89
|
+
|
|
90
|
+
prompt = (
|
|
91
|
+
f"You are the FluxState Autonomous Intelligence Nexus, an advanced OSINT and Threat Analysis system.\n"
|
|
92
|
+
f"Perform forensic analysis on the provided telemetry logs. Respond with tactical precision, identifying critical events, target trajectories, and behavioral anomalies.\n\n"
|
|
93
|
+
f"Operator Query: {natural_language_query}\n\n"
|
|
94
|
+
f"Telemetry Logs:\n{context_block}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Since this is a text-only query, we don't pass images.
|
|
98
|
+
messages = [{"role": "user", "content": prompt}]
|
|
99
|
+
formatted_prompt = self.processor.apply_chat_template(
|
|
100
|
+
messages, tokenize=False, add_generation_prompt=True
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Execute inference on the Apple Silicon GPU
|
|
104
|
+
response = generate(self.model, self.processor, formatted_prompt, verbose=False)
|
|
105
|
+
return response.text if hasattr(response, 'text') else str(response)
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
return f"[Agent Error] {str(e)}"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# core/engine.py
|
|
2
|
+
import time
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import mlx.core as mx
|
|
8
|
+
MLX_AVAILABLE = True
|
|
9
|
+
except ImportError:
|
|
10
|
+
MLX_AVAILABLE = False
|
|
11
|
+
print("[Warning] MLX not available. Falling back to simulated arrays for development.")
|
|
12
|
+
|
|
13
|
+
class FluxInferenceEngine:
|
|
14
|
+
"""
|
|
15
|
+
Interfaces directly with Apple Silicon using MLX, orchestrating the local reasoning engine.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
print(f"[Engine] Initializing local backend on Apple Silicon...")
|
|
19
|
+
|
|
20
|
+
def _load_policy(self):
|
|
21
|
+
try:
|
|
22
|
+
import os
|
|
23
|
+
policy_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "intelligence_policy.json")
|
|
24
|
+
with open(policy_path, "r") as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
except Exception:
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
def generate_context_reasoning(self, detected_objects, movement_boxes, action_heuristic, rf_count=0, acoustic_event="AMBIENT") -> str:
|
|
30
|
+
"""
|
|
31
|
+
Takes the raw semantic telemetry (YOLO objects + Skeletal Actions + RF + Audio + Identity) and
|
|
32
|
+
uses the local LLM to reason about the context based on a Dynamic Policy Engine.
|
|
33
|
+
"""
|
|
34
|
+
time.sleep(0.05) # Simulate inference latency
|
|
35
|
+
|
|
36
|
+
# Hot-reload Intelligence Policy
|
|
37
|
+
policy = self._load_policy()
|
|
38
|
+
vectors = policy.get("THREAT_VECTORS", {})
|
|
39
|
+
|
|
40
|
+
persons = [obj for obj in detected_objects if "person" in obj[4].lower()]
|
|
41
|
+
other_objects = [obj for obj in detected_objects if "person" not in obj[4].lower()]
|
|
42
|
+
|
|
43
|
+
# Extract Cryptographic GPASS Tokens
|
|
44
|
+
gpass_list = []
|
|
45
|
+
for p in persons:
|
|
46
|
+
if "[" in p[4] and "]" in p[4]:
|
|
47
|
+
gpass = p[4].split("[")[-1].replace("]", "").strip()
|
|
48
|
+
if "GPX" in gpass:
|
|
49
|
+
gpass_list.append(gpass)
|
|
50
|
+
|
|
51
|
+
# --- BEHAVIORAL THREAT MATRIX (Dynamic OTA Policy) ---
|
|
52
|
+
threat_flags = []
|
|
53
|
+
|
|
54
|
+
# 1. Kinetic-Acoustic Fusion Threat
|
|
55
|
+
kinetic_rule = vectors.get("KINETIC_AGGRESSION", {})
|
|
56
|
+
if kinetic_rule.get("enabled", False):
|
|
57
|
+
if action_heuristic in kinetic_rule.get("trigger_actions", []) and acoustic_event in kinetic_rule.get("trigger_acoustics", []):
|
|
58
|
+
threat_flags.append("KINETIC_AGGRESSION")
|
|
59
|
+
|
|
60
|
+
# 2. RF Anomaly (1 person carrying multiple network emitting devices)
|
|
61
|
+
rf_rule = vectors.get("RF_PAYLOAD_ANOMALY", {})
|
|
62
|
+
if rf_rule.get("enabled", False):
|
|
63
|
+
if len(persons) == 1 and rf_count > rf_rule.get("max_allowed_rf_per_entity", 3):
|
|
64
|
+
threat_flags.append("RF_DEVICE_ANOMALY")
|
|
65
|
+
|
|
66
|
+
# 3. Unauthorized Area / Abandoned Object Logic can be injected here
|
|
67
|
+
|
|
68
|
+
threat_str = ""
|
|
69
|
+
db_status = ""
|
|
70
|
+
if len(gpass_list) > 0:
|
|
71
|
+
gpass_str = f" (Identities: {', '.join(set(gpass_list))})"
|
|
72
|
+
if len(threat_flags) > 0:
|
|
73
|
+
threat_str = f" [THREAT VECTOR: {', '.join(threat_flags)}]"
|
|
74
|
+
db_status = f" [SWARM LEDGER: BEHAVIORAL ANOMALY DETECTED. GPASS FLAG PROMOTED]"
|
|
75
|
+
else:
|
|
76
|
+
db_status = f" [SWARM LEDGER: CONTEXT NOMINAL. BEHAVIOR CLEARED]"
|
|
77
|
+
else:
|
|
78
|
+
gpass_str = ""
|
|
79
|
+
|
|
80
|
+
rf_str = f" [RF Intel: {rf_count} devices]" if rf_count > 0 else ""
|
|
81
|
+
audio_str = f" [Acoustic: {acoustic_event}]"
|
|
82
|
+
|
|
83
|
+
if len(persons) == 0:
|
|
84
|
+
if len(other_objects) > 0:
|
|
85
|
+
labels = set(f"{obj[4]} ({obj[5]:.2f})" for obj in other_objects)
|
|
86
|
+
return f"No humans detected. Present objects: {', '.join(labels)}.{rf_str}{audio_str}"
|
|
87
|
+
elif len(movement_boxes) > 0:
|
|
88
|
+
return f"Non-human kinetic activity detected. Unclassified environmental shift.{rf_str}{audio_str}"
|
|
89
|
+
return f"Environment stable. No significant entities present.{rf_str}{audio_str}"
|
|
90
|
+
|
|
91
|
+
if len(persons) == 1:
|
|
92
|
+
if len(other_objects) > 0:
|
|
93
|
+
labels = set(f"{obj[4]} ({obj[5]:.2f})" for obj in other_objects)
|
|
94
|
+
return f"1 Individual{gpass_str} [Action: {action_heuristic}]. Proximal Objects: {', '.join(labels)}.{rf_str}{audio_str}{threat_str}{db_status}"
|
|
95
|
+
return f"1 Individual{gpass_str} present. Primary Action: {action_heuristic}.{rf_str}{audio_str}{threat_str}{db_status}"
|
|
96
|
+
|
|
97
|
+
if len(persons) > 1:
|
|
98
|
+
return f"{len(persons)} Individuals{gpass_str} detected. Primary collective action: {action_heuristic}.{rf_str}{audio_str}{threat_str}{db_status}"
|