mrlis1337

Untitled

Jul 12th, 2026
40
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.16 KB | None | 0 0
  1. import sys
  2. import socket
  3. import json
  4. import numpy as np
  5. from collections import deque
  6. import pyqtgraph as pg
  7. from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QLabel
  8. from PyQt6.QtCore import QTimer, Qt
  9.  
  10.  
  11. WIN = 64
  12. MASK_CALIB = 50
  13. BASELINE_CALIB = 150
  14. NULL_FRACTION = 0.20
  15. HAMPEL_GAMMA = 3.0
  16. Z_THRESH = 8.0
  17. METRIC_SCALE = 1000.0
  18.  
  19.  
  20. class CSIProcessor:
  21.  
  22. def __init__(self, name):
  23. self.name = name
  24. self.expected_len = None
  25. self.active_mask = None
  26. self._mask_sum = None
  27. self._mask_cnt = 0
  28. self._drop_first = False
  29.  
  30. self.win = deque(maxlen=WIN)
  31. self._calib = deque(maxlen=BASELINE_CALIB)
  32. self.baseline_med = None
  33. self.baseline_mad = None
  34.  
  35. @staticmethod
  36. def _amplitudes(raw):
  37. arr = np.asarray(raw, dtype=np.float64)
  38. n = arr.size - (arr.size & 1)
  39. iq = arr[:n].reshape(-1, 2)
  40. return np.sqrt(iq[:, 0] ** 2 + iq[:, 1] ** 2)
  41.  
  42. def calibrating(self):
  43. return self.baseline_med is None
  44.  
  45. def process(self, raw):
  46. if isinstance(raw, dict):
  47. csi = raw.get("csi")
  48. if raw.get("first_word_invalid"):
  49. if not self._drop_first:
  50. self._drop_first = True
  51. if self.active_mask is not None:
  52. self.active_mask[0:2] = False
  53. else:
  54. csi = raw
  55.  
  56. if not isinstance(csi, list) or len(csi) < 128:
  57. return None
  58.  
  59. if self.expected_len is None:
  60. self.expected_len = len(csi)
  61. if len(csi) != self.expected_len:
  62. return None
  63.  
  64. amp = self._amplitudes(csi)
  65.  
  66. if self._drop_first and amp.size >= 2:
  67. amp[0] = 0.0
  68. amp[1] = 0.0
  69.  
  70. if self.active_mask is None:
  71. if self._mask_sum is None:
  72. self._mask_sum = np.zeros_like(amp)
  73. self._mask_sum += amp
  74. self._mask_cnt += 1
  75. if self._mask_cnt < MASK_CALIB:
  76. return None
  77. mean_amp = self._mask_sum / self._mask_cnt
  78. ref = np.median(mean_amp[mean_amp > 0]) if np.any(mean_amp > 0) else 0.0
  79. self.active_mask = mean_amp > (NULL_FRACTION * ref)
  80. if self.active_mask.sum() < 4:
  81. self.active_mask = mean_amp > 0
  82. amp = amp[self.active_mask]
  83.  
  84. m = amp.mean()
  85. if m <= 1e-9:
  86. return None
  87. amp_n = amp / m
  88.  
  89. self.win.append(amp_n)
  90. if len(self.win) < WIN:
  91. return None
  92. W = np.stack(self.win)
  93.  
  94. med = np.median(W, axis=0)
  95. mad = np.median(np.abs(W - med), axis=0)
  96. thr = HAMPEL_GAMMA * 1.4826 * mad + 1e-12
  97. Wc = np.where(np.abs(W - med) > thr, med, W)
  98.  
  99. metric = float(np.mean(np.var(Wc, axis=0))) * METRIC_SCALE
  100.  
  101. if self.baseline_med is None:
  102. self._calib.append(metric)
  103. if len(self._calib) >= BASELINE_CALIB:
  104. b = np.asarray(self._calib)
  105. self.baseline_med = float(np.median(b))
  106. self.baseline_mad = float(np.median(np.abs(b - self.baseline_med)) * 1.4826 + 1e-9)
  107. return (metric, 0.0)
  108.  
  109. z = (metric - self.baseline_med) / self.baseline_mad
  110. return (metric, z)
  111.  
  112.  
  113. class DualRadarUI(QMainWindow):
  114. def __init__(self):
  115. super().__init__()
  116. self.setWindowTitle("GhostWatch - Dual Zone Radar")
  117. self.resize(1000, 700)
  118. self.setStyleSheet("background-color: #0a0a0a; color: #00FF00; font-family: Consolas;")
  119.  
  120. self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  121. self.sock_a.bind(("127.0.0.1", 5005))
  122. self.sock_a.setblocking(False)
  123.  
  124. self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  125. self.sock_b.bind(("127.0.0.1", 5006))
  126. self.sock_b.setblocking(False)
  127.  
  128. self.proc_a = CSIProcessor("ZONE A")
  129. self.proc_b = CSIProcessor("ZONE B")
  130.  
  131. self.plot_data_a = np.zeros(200)
  132. self.plot_data_b = np.zeros(200)
  133.  
  134. self.init_ui()
  135.  
  136. self.timer = QTimer()
  137. self.timer.timeout.connect(self.update_radar)
  138. self.timer.start(30)
  139.  
  140. def init_ui(self):
  141. main_layout = QVBoxLayout()
  142. graphs_layout = QHBoxLayout()
  143.  
  144. pg.setConfigOption('background', '#0a0a0a')
  145. pg.setConfigOption('foreground', '#00FF00')
  146.  
  147. self.plot_widget_a = pg.PlotWidget(title="СЕКТОР А: энергия движения")
  148. self.curve_a = self.plot_widget_a.plot(pen=pg.mkPen('#00FF00', width=2))
  149. self.thr_a = self.plot_widget_a.addLine(y=0, pen=pg.mkPen('#FFAA00', width=1, style=Qt.PenStyle.DashLine))
  150. graphs_layout.addWidget(self.plot_widget_a)
  151.  
  152. self.plot_widget_b = pg.PlotWidget(title="СЕКТОР Б: энергия движения")
  153. self.curve_b = self.plot_widget_b.plot(pen=pg.mkPen('#00FF00', width=2))
  154. self.thr_b = self.plot_widget_b.addLine(y=0, pen=pg.mkPen('#FFAA00', width=1, style=Qt.PenStyle.DashLine))
  155. graphs_layout.addWidget(self.plot_widget_b)
  156.  
  157. main_layout.addLayout(graphs_layout)
  158. map_layout = QHBoxLayout()
  159.  
  160. self.alert_a = QLabel("ZONE A: CALIBRATING…")
  161. self.alert_a.setAlignment(Qt.AlignmentFlag.AlignCenter)
  162. self.alert_a.setStyleSheet("background-color: #002200; border: 2px solid #00FF00; font-size: 32px; font-weight: bold; padding: 20px;")
  163.  
  164. self.alert_b = QLabel("ZONE B: CALIBRATING…")
  165. self.alert_b.setAlignment(Qt.AlignmentFlag.AlignCenter)
  166. self.alert_b.setStyleSheet("background-color: #002200; border: 2px solid #00FF00; font-size: 32px; font-weight: bold; padding: 20px;")
  167.  
  168. map_layout.addWidget(self.alert_a)
  169. map_layout.addWidget(self.alert_b)
  170.  
  171. main_layout.addLayout(map_layout)
  172. container = QWidget()
  173. container.setLayout(main_layout)
  174. self.setCentralWidget(container)
  175.  
  176. def process_stream(self, sock, proc, plot_data, curve, thr_line, alert_label):
  177. try:
  178. while True:
  179. data, _ = sock.recvfrom(65536)
  180. try:
  181. raw_csi = json.loads(data.decode('utf-8'))
  182. except (ValueError, UnicodeDecodeError):
  183. continue
  184.  
  185. res = proc.process(raw_csi)
  186. if res is None:
  187. continue
  188. metric, z = res
  189.  
  190. plot_data[:-1] = plot_data[1:]
  191. plot_data[-1] = metric
  192. curve.setData(plot_data)
  193.  
  194. if proc.calibrating():
  195. alert_label.setText(f"{proc.name}: CALIBRATING… ({len(proc._calib)}/{BASELINE_CALIB})")
  196. alert_label.setStyleSheet("background-color: #111100; color: #FFAA00; border: 2px solid #FFAA00; font-size: 30px; font-weight: bold; padding: 20px;")
  197. continue
  198.  
  199. thr_val = proc.baseline_med + Z_THRESH * proc.baseline_mad
  200. thr_line.setValue(thr_val)
  201.  
  202. if z > Z_THRESH:
  203. alert_label.setText(f"!! {proc.name} BREACHED !!")
  204. alert_label.setStyleSheet("background-color: #440000; color: #FF0000; border: 2px solid #FF0000; font-size: 32px; font-weight: bold; padding: 20px;")
  205. curve.setPen(pg.mkPen('#FF0000', width=3))
  206. else:
  207. alert_label.setText(f"{proc.name}: CLEAR")
  208. alert_label.setStyleSheet("background-color: #002200; color: #00FF00; border: 2px solid #00FF00; font-size: 32px; font-weight: bold; padding: 20px;")
  209. curve.setPen(pg.mkPen('#00FF00', width=2))
  210. except BlockingIOError:
  211. pass
  212.  
  213. def update_radar(self):
  214. self.process_stream(self.sock_a, self.proc_a, self.plot_data_a, self.curve_a, self.thr_a, self.alert_a)
  215. self.process_stream(self.sock_b, self.proc_b, self.plot_data_b, self.curve_b, self.thr_b, self.alert_b)
  216.  
  217.  
  218. if __name__ == '__main__':
  219. app = QApplication(sys.argv)
  220. window = DualRadarUI()
  221. window.show()
  222. sys.exit(app.exec())
  223.  
Advertisement
Add Comment
Please, Sign In to add comment