|
|
|
|
@ -1,7 +1,10 @@
|
|
|
|
|
import os
|
|
|
|
|
import math
|
|
|
|
|
import statistics
|
|
|
|
|
|
|
|
|
|
# Более лучшая версия кода есть в FRScanner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataBuffer:
|
|
|
|
|
"""
|
|
|
|
|
Класс с реализацией циклического буффера.
|
|
|
|
|
@ -13,9 +16,8 @@ class DataBuffer:
|
|
|
|
|
num_of_thinning_iter: Прореживающий множитель. Раз в это количечество раз будет обнволяться столбец буфера.
|
|
|
|
|
line_size: Количество строк буфера = количеству каналов.
|
|
|
|
|
columns_size: Количество столбцов = фиксированное число.
|
|
|
|
|
multiply_factor: Процентный показатель превышения сигналом уровня шума. ex m_p = 1.1 => триггер, если
|
|
|
|
|
сигнал превышает шум на 10%.
|
|
|
|
|
num_for_alarm: Количество раз, превышающих шум, при которых триггеримся = фиксированное число.
|
|
|
|
|
multiply_factor: Процентный показатель превышения сигналом уровня шума.
|
|
|
|
|
num_for_alarm: Количество раз, превышающих шум, при которых триггеримся.
|
|
|
|
|
is_init: Флаг инициализации буфера. = True, если инициализирован.
|
|
|
|
|
buffer: Массив для буфера.
|
|
|
|
|
buffer_medians: Массив для медиан столбцов букера.
|
|
|
|
|
@ -45,6 +47,16 @@ class DataBuffer:
|
|
|
|
|
self.buffer_medians = [0] * self.line_size
|
|
|
|
|
self.buffer_alarms = [0] * self.line_size
|
|
|
|
|
|
|
|
|
|
self.prev_values = [None] * self.line_size
|
|
|
|
|
self.trend_streak = [0] * self.line_size
|
|
|
|
|
|
|
|
|
|
# Рост в 15% по линейной мощности относительно фоновой медианы в dBFS.
|
|
|
|
|
self.dbfs_delta_ratio = float(os.getenv('dbfs_delta_percent', 15)) / 100.0
|
|
|
|
|
# Допускаем небольшой обратный ход, чтобы не сбрасываться от микрошума.
|
|
|
|
|
self.dbfs_max_backstep_db = float(os.getenv('dbfs_max_backstep_db', 0.25))
|
|
|
|
|
# Минимум подряд "плавных" шагов перед учетом как устойчивого роста.
|
|
|
|
|
self.dbfs_min_trend_steps = int(os.getenv('dbfs_min_trend_steps', max(1, self.num_for_alarm)))
|
|
|
|
|
|
|
|
|
|
def get_buffer(self):
|
|
|
|
|
return self.buffer
|
|
|
|
|
|
|
|
|
|
@ -71,11 +83,16 @@ class DataBuffer:
|
|
|
|
|
if self.check_init():
|
|
|
|
|
for i in range(self.line_size):
|
|
|
|
|
self.buffer_medians[i] = statistics.median(self.buffer[i])
|
|
|
|
|
# print('medians is: ', self.buffer_medians)
|
|
|
|
|
# return self.buffer_medians
|
|
|
|
|
|
|
|
|
|
def alarms_fill_zeros(self):
|
|
|
|
|
self.buffer_alarms = [0] * self.line_size
|
|
|
|
|
self.trend_streak = [0] * self.line_size
|
|
|
|
|
self.prev_values = [None] * self.line_size
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _dbfs_growth_ratio(current_db, baseline_db):
|
|
|
|
|
return math.pow(10.0, (current_db - baseline_db) / 10.0) - 1.0
|
|
|
|
|
|
|
|
|
|
def update(self, data):
|
|
|
|
|
"""
|
|
|
|
|
Обновление буфера.
|
|
|
|
|
@ -85,26 +102,17 @@ class DataBuffer:
|
|
|
|
|
3. Берем медианы по буферу, если он уже проиницализирован.
|
|
|
|
|
4. Сбрасываем счетчик текущих чтений.
|
|
|
|
|
5. Если был последний столбец (и мы уже переключились на первый), то
|
|
|
|
|
Если прореживающий множитель на текующей итерации был единица, то мы иницилизировались
|
|
|
|
|
Если прореживающий множитель на текующей итерации был единица, то мы иницилизировались.
|
|
|
|
|
До тех пор, пока множитель на итерации меньше фиксированного, увеличиваем в два раза.
|
|
|
|
|
В противном случае - увеличиваем номер чтения.
|
|
|
|
|
:param data: Массив с метриками сигнала по каналам.
|
|
|
|
|
:return: None
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# TODO: Добавить время релаксации - если система затриггерилась, то перестать обновлять буфер на N чтений,
|
|
|
|
|
# где N задается в .env-template. Сейчас есть бага, что буфер перестает обновляться только когда система
|
|
|
|
|
# триггерится. Между тем, когда приходит аларм и num_for_alarm, когда сигнал алармовский, но система еще не
|
|
|
|
|
# триггерится, буфер продолжает обновляться. В таких условиях буфер может набрать в себя алармовских сигналов
|
|
|
|
|
# и повысить пороги. Пример такой ситуации: дрон висит на 1км, система его видит, но сигнал превышает порог раз
|
|
|
|
|
# через раз и аларм срабатывает не всегда. В таких условиях наберется высокий сигнал, повысятся пороги и когда
|
|
|
|
|
# дрон начнет движение вперед, он будет заметен на более низкой дистанции, чем обычно, так как пороги повышены.
|
|
|
|
|
|
|
|
|
|
if self.current_counter == self.thinning_counter:
|
|
|
|
|
for i in range(self.line_size):
|
|
|
|
|
self.buffer[i][self.current_column] = data[i]
|
|
|
|
|
self.current_column = (self.current_column + 1) % self.columns_size
|
|
|
|
|
#print('Столбец {0} обновлен. Перешли к столбцу {1}: '.format(self.current_column - 1, self.current_column))
|
|
|
|
|
self.medians()
|
|
|
|
|
self.current_counter = 1
|
|
|
|
|
if self.current_column == 0:
|
|
|
|
|
@ -114,56 +122,60 @@ class DataBuffer:
|
|
|
|
|
print('Начальная калибровка завершена.')
|
|
|
|
|
if self.thinning_counter < self.num_of_thinning_iter:
|
|
|
|
|
self.thinning_counter *= 2
|
|
|
|
|
# print('thinning counter обновлен: ', self.thinning_counter)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self.current_counter += 1
|
|
|
|
|
# print('curr counter обновлен: ', self.current_counter)
|
|
|
|
|
|
|
|
|
|
def check_alarm(self, data):
|
|
|
|
|
"""
|
|
|
|
|
Проверка триггера системы.
|
|
|
|
|
Если значение по каналу превышает медиану (порог) шума на какой-то процент, то инкремент буфер аларма по каналу.
|
|
|
|
|
Превышение num_for_alarm подряд - триггер. Если после n превышений, где n<num_for_alarm приходит сигнал не
|
|
|
|
|
первышающий порог, то сбрасываем буфер алармов.
|
|
|
|
|
:param data:
|
|
|
|
|
:return: Да/нет.
|
|
|
|
|
Проверка триггера системы по dBFS во времени.
|
|
|
|
|
Триггер: устойчивый рост относительно фоновой медианы не меньше dbfs_delta_percent,
|
|
|
|
|
подтвержденный несколькими последовательными чтениями.
|
|
|
|
|
"""
|
|
|
|
|
if self.check_init():
|
|
|
|
|
ratios=[]
|
|
|
|
|
print("="*50)
|
|
|
|
|
for i in range(len(data)):
|
|
|
|
|
exceeding = data[i] > self.multiply_factor * self.buffer_medians[i]
|
|
|
|
|
ratios.append(data[i]/self.buffer_medians[i])
|
|
|
|
|
baseline = self.buffer_medians[i]
|
|
|
|
|
current = data[i]
|
|
|
|
|
|
|
|
|
|
growth_ratio = self._dbfs_growth_ratio(current, baseline)
|
|
|
|
|
|
|
|
|
|
prev = self.prev_values[i]
|
|
|
|
|
delta_db = 0.0 if prev is None else current - prev
|
|
|
|
|
monotonic_or_stable = (prev is None) or (delta_db >= -self.dbfs_max_backstep_db)
|
|
|
|
|
|
|
|
|
|
if monotonic_or_stable:
|
|
|
|
|
self.trend_streak[i] += 1
|
|
|
|
|
else:
|
|
|
|
|
self.trend_streak[i] = 0
|
|
|
|
|
|
|
|
|
|
exceeding = (
|
|
|
|
|
growth_ratio >= self.dbfs_delta_ratio
|
|
|
|
|
and self.trend_streak[i] >= self.dbfs_min_trend_steps
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if exceeding:
|
|
|
|
|
self.buffer_alarms[i] += 1
|
|
|
|
|
# print('Инкремент буффер алармов по каналу {0}, текущее число по этому каналу: {1}'.format(i,self.buffer_alarms[i]))
|
|
|
|
|
else:
|
|
|
|
|
self.buffer_alarms[i] = 0
|
|
|
|
|
# print('Обнулили буффер алармов по каналу {0}, текущее число по этому каналу: {1}'.format(i,self.buffer_alarms[i]))
|
|
|
|
|
|
|
|
|
|
self.prev_values[i] = current
|
|
|
|
|
|
|
|
|
|
if self.buffer_alarms[i] >= self.num_for_alarm:
|
|
|
|
|
# print('Сработала тревога по каналу {0}, текущее число по этому каналу: {1}'.format(i,self.buffer_alarms[i]))
|
|
|
|
|
self.buffer_alarms = [0] * self.line_size
|
|
|
|
|
print("Отношения:", [f"{r:.3f}" for r in ratios])
|
|
|
|
|
print("!"*50)
|
|
|
|
|
self.trend_streak = [0] * self.line_size
|
|
|
|
|
return True
|
|
|
|
|
print("Отношения:", [f"{r:.3f}" for r in ratios])
|
|
|
|
|
print("="*50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def check_single_alarm(self, median, cur_channel):
|
|
|
|
|
"""
|
|
|
|
|
Проверка, является ли текущая медиана по каналу превышающей порог.
|
|
|
|
|
:param median: меди (хар-ка) по каналу.
|
|
|
|
|
Проверка, является ли текущая метрика по каналу превышающей порог роста.
|
|
|
|
|
:param median: текущая метрика в dBFS.
|
|
|
|
|
:param cur_channel: индекс канала внутри частоты.
|
|
|
|
|
:return: Да/нет.
|
|
|
|
|
"""
|
|
|
|
|
if self.check_init():
|
|
|
|
|
exceeding = median > self.multiply_factor * self.buffer_medians[cur_channel]
|
|
|
|
|
print(median/self.buffer_medians[cur_channel])
|
|
|
|
|
baseline = self.buffer_medians[cur_channel]
|
|
|
|
|
exceeding = self._dbfs_growth_ratio(median, baseline) >= self.dbfs_delta_ratio
|
|
|
|
|
if exceeding:
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
|