Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

551 rinda
14KB

  1. package detector
  2. import (
  3. "math"
  4. "sort"
  5. "time"
  6. "sdr-visual-suite/internal/cfar"
  7. "sdr-visual-suite/internal/classifier"
  8. "sdr-visual-suite/internal/config"
  9. )
  10. type Event struct {
  11. ID int64 `json:"id"`
  12. Start time.Time `json:"start"`
  13. End time.Time `json:"end"`
  14. CenterHz float64 `json:"center_hz"`
  15. Bandwidth float64 `json:"bandwidth_hz"`
  16. PeakDb float64 `json:"peak_db"`
  17. SNRDb float64 `json:"snr_db"`
  18. FirstBin int `json:"first_bin"`
  19. LastBin int `json:"last_bin"`
  20. Class *classifier.Classification `json:"class,omitempty"`
  21. }
  22. type Detector struct {
  23. ThresholdDb float64
  24. MinDuration time.Duration
  25. Hold time.Duration
  26. EmaAlpha float64
  27. HysteresisDb float64
  28. MinStableFrames int
  29. GapTolerance time.Duration
  30. CFARScaleDb float64
  31. EdgeMarginDb float64
  32. MaxSignalBwHz float64
  33. MergeGapHz float64
  34. binWidth float64
  35. nbins int
  36. sampleRate int
  37. ema []float64
  38. active map[int64]*activeEvent
  39. nextID int64
  40. cfarEngine cfar.CFAR
  41. lastThresholds []float64
  42. lastNoiseFloor float64
  43. }
  44. type activeEvent struct {
  45. id int64
  46. start time.Time
  47. lastSeen time.Time
  48. centerHz float64
  49. bwHz float64
  50. peakDb float64
  51. snrDb float64
  52. firstBin int
  53. lastBin int
  54. class *classifier.Classification
  55. stableHits int
  56. }
  57. type Signal struct {
  58. FirstBin int `json:"first_bin"`
  59. LastBin int `json:"last_bin"`
  60. CenterHz float64 `json:"center_hz"`
  61. BWHz float64 `json:"bw_hz"`
  62. PeakDb float64 `json:"peak_db"`
  63. SNRDb float64 `json:"snr_db"`
  64. NoiseDb float64 `json:"noise_db,omitempty"`
  65. Class *classifier.Classification `json:"class,omitempty"`
  66. }
  67. func New(detCfg config.DetectorConfig, sampleRate int, fftSize int) *Detector {
  68. minDur := time.Duration(detCfg.MinDurationMs) * time.Millisecond
  69. hold := time.Duration(detCfg.HoldMs) * time.Millisecond
  70. gapTolerance := time.Duration(detCfg.GapToleranceMs) * time.Millisecond
  71. emaAlpha := detCfg.EmaAlpha
  72. hysteresis := detCfg.HysteresisDb
  73. minStable := detCfg.MinStableFrames
  74. cfarMode := detCfg.CFARMode
  75. cfarGuard := detCfg.CFARGuardCells
  76. cfarTrain := detCfg.CFARTrainCells
  77. cfarRank := detCfg.CFARRank
  78. cfarScaleDb := detCfg.CFARScaleDb
  79. cfarWrap := detCfg.CFARWrapAround
  80. thresholdDb := detCfg.ThresholdDb
  81. edgeMarginDb := detCfg.EdgeMarginDb
  82. maxSignalBwHz := detCfg.MaxSignalBwHz
  83. mergeGapHz := detCfg.MergeGapHz
  84. if minDur <= 0 {
  85. minDur = 250 * time.Millisecond
  86. }
  87. if hold <= 0 {
  88. hold = 500 * time.Millisecond
  89. }
  90. if emaAlpha <= 0 || emaAlpha > 1 {
  91. emaAlpha = 0.2
  92. }
  93. if hysteresis <= 0 {
  94. hysteresis = 3
  95. }
  96. if minStable <= 0 {
  97. minStable = 3
  98. }
  99. if gapTolerance <= 0 {
  100. gapTolerance = hold
  101. }
  102. if cfarGuard < 0 {
  103. cfarGuard = 2
  104. }
  105. if cfarTrain <= 0 {
  106. cfarTrain = 16
  107. }
  108. if cfarScaleDb <= 0 {
  109. cfarScaleDb = 6
  110. }
  111. if edgeMarginDb <= 0 {
  112. edgeMarginDb = 3.0
  113. }
  114. if maxSignalBwHz <= 0 {
  115. maxSignalBwHz = 150000
  116. }
  117. if mergeGapHz <= 0 {
  118. mergeGapHz = 5000
  119. }
  120. if cfarRank <= 0 || cfarRank > 2*cfarTrain {
  121. cfarRank = int(math.Round(0.75 * float64(2*cfarTrain)))
  122. if cfarRank <= 0 {
  123. cfarRank = 1
  124. }
  125. }
  126. var cfarEngine cfar.CFAR
  127. if cfarMode != "" && cfarMode != "OFF" {
  128. cfarEngine = cfar.New(cfar.Config{
  129. Mode: cfar.Mode(cfarMode),
  130. GuardCells: cfarGuard,
  131. TrainCells: cfarTrain,
  132. Rank: cfarRank,
  133. ScaleDb: cfarScaleDb,
  134. WrapAround: cfarWrap,
  135. })
  136. }
  137. return &Detector{
  138. ThresholdDb: thresholdDb,
  139. MinDuration: minDur,
  140. Hold: hold,
  141. EmaAlpha: emaAlpha,
  142. HysteresisDb: hysteresis,
  143. MinStableFrames: minStable,
  144. GapTolerance: gapTolerance,
  145. CFARScaleDb: cfarScaleDb,
  146. EdgeMarginDb: edgeMarginDb,
  147. MaxSignalBwHz: maxSignalBwHz,
  148. MergeGapHz: mergeGapHz,
  149. binWidth: float64(sampleRate) / float64(fftSize),
  150. nbins: fftSize,
  151. sampleRate: sampleRate,
  152. ema: make([]float64, fftSize),
  153. active: map[int64]*activeEvent{},
  154. nextID: 1,
  155. cfarEngine: cfarEngine,
  156. }
  157. }
  158. func (d *Detector) Process(now time.Time, spectrum []float64, centerHz float64) ([]Event, []Signal) {
  159. signals := d.detectSignals(spectrum, centerHz)
  160. finished := d.matchSignals(now, signals)
  161. return finished, signals
  162. }
  163. func (d *Detector) LastThresholds() []float64 {
  164. if len(d.lastThresholds) == 0 {
  165. return nil
  166. }
  167. return append([]float64(nil), d.lastThresholds...)
  168. }
  169. func (d *Detector) LastNoiseFloor() float64 {
  170. return d.lastNoiseFloor
  171. }
  172. func (d *Detector) UpdateClasses(signals []Signal) {
  173. for _, s := range signals {
  174. for _, ev := range d.active {
  175. if overlapHz(s.CenterHz, s.BWHz, ev.centerHz, ev.bwHz) && math.Abs(s.CenterHz-ev.centerHz) < (s.BWHz+ev.bwHz)/2.0 {
  176. if s.Class != nil {
  177. if ev.class == nil || s.Class.Confidence >= ev.class.Confidence {
  178. ev.class = s.Class
  179. }
  180. }
  181. }
  182. }
  183. }
  184. }
  185. func (d *Detector) detectSignals(spectrum []float64, centerHz float64) []Signal {
  186. n := len(spectrum)
  187. if n == 0 {
  188. return nil
  189. }
  190. smooth := d.smoothSpectrum(spectrum)
  191. var thresholds []float64
  192. if d.cfarEngine != nil {
  193. thresholds = d.cfarEngine.Thresholds(smooth)
  194. }
  195. d.lastThresholds = append(d.lastThresholds[:0], thresholds...)
  196. noiseGlobal := median(smooth)
  197. d.lastNoiseFloor = noiseGlobal
  198. var signals []Signal
  199. in := false
  200. start := 0
  201. peak := -1e9
  202. peakBin := 0
  203. for i := 0; i < n; i++ {
  204. v := smooth[i]
  205. thresholdOn := d.ThresholdDb
  206. if thresholds != nil && !math.IsNaN(thresholds[i]) {
  207. thresholdOn = thresholds[i]
  208. }
  209. thresholdOff := thresholdOn - d.HysteresisDb
  210. if v >= thresholdOn {
  211. if !in {
  212. in = true
  213. start = i
  214. peak = v
  215. peakBin = i
  216. } else if v > peak {
  217. peak = v
  218. peakBin = i
  219. }
  220. } else if in && v < thresholdOff {
  221. noise := noiseGlobal
  222. if thresholds != nil && peakBin >= 0 && peakBin < len(thresholds) && !math.IsNaN(thresholds[peakBin]) {
  223. noise = thresholds[peakBin] - d.CFARScaleDb
  224. }
  225. signals = append(signals, d.makeSignal(start, i-1, peak, peakBin, noise, centerHz, smooth))
  226. in = false
  227. }
  228. }
  229. if in {
  230. noise := noiseGlobal
  231. if thresholds != nil && peakBin >= 0 && peakBin < len(thresholds) && !math.IsNaN(thresholds[peakBin]) {
  232. noise = thresholds[peakBin] - d.CFARScaleDb
  233. }
  234. signals = append(signals, d.makeSignal(start, n-1, peak, peakBin, noise, centerHz, smooth))
  235. }
  236. signals = d.expandSignalEdges(signals, smooth, noiseGlobal, centerHz)
  237. for i := range signals {
  238. centerBin := float64(signals[i].FirstBin+signals[i].LastBin) / 2.0
  239. signals[i].CenterHz = d.centerFreqForBin(centerBin, centerHz)
  240. signals[i].BWHz = float64(signals[i].LastBin-signals[i].FirstBin+1) * d.binWidth
  241. }
  242. return signals
  243. }
  244. func (d *Detector) expandSignalEdges(signals []Signal, smooth []float64, noiseFloor float64, centerHz float64) []Signal {
  245. n := len(smooth)
  246. if n == 0 || len(signals) == 0 {
  247. return signals
  248. }
  249. margin := d.EdgeMarginDb
  250. if margin <= 0 {
  251. margin = 3.0
  252. }
  253. maxExpansionBins := int(d.MaxSignalBwHz / d.binWidth)
  254. if maxExpansionBins < 10 {
  255. maxExpansionBins = 10
  256. }
  257. for i := range signals {
  258. seed := signals[i]
  259. peakDb := seed.PeakDb
  260. localNoise := noiseFloor
  261. leftProbe := seed.FirstBin - 50
  262. rightProbe := seed.LastBin + 50
  263. if leftProbe >= 0 && rightProbe < n {
  264. leftNoise := minInRange(smooth, maxInt(0, leftProbe), maxInt(0, seed.FirstBin-5))
  265. rightNoise := minInRange(smooth, minInt(n-1, seed.LastBin+5), minInt(n-1, rightProbe))
  266. localNoise = math.Min(leftNoise, rightNoise)
  267. }
  268. edgeThreshold := localNoise + margin
  269. newFirst := seed.FirstBin
  270. prevVal := smooth[newFirst]
  271. for j := 0; j < maxExpansionBins; j++ {
  272. next := newFirst - 1
  273. if next < 0 {
  274. break
  275. }
  276. val := smooth[next]
  277. if val <= edgeThreshold {
  278. break
  279. }
  280. if val > prevVal+1.0 && val < peakDb-6.0 {
  281. break
  282. }
  283. prevVal = val
  284. newFirst = next
  285. }
  286. newLast := seed.LastBin
  287. prevVal = smooth[newLast]
  288. for j := 0; j < maxExpansionBins; j++ {
  289. next := newLast + 1
  290. if next >= n {
  291. break
  292. }
  293. val := smooth[next]
  294. if val <= edgeThreshold {
  295. break
  296. }
  297. if val > prevVal+1.0 && val < peakDb-6.0 {
  298. break
  299. }
  300. prevVal = val
  301. newLast = next
  302. }
  303. signals[i].FirstBin = newFirst
  304. signals[i].LastBin = newLast
  305. centerBin := float64(newFirst+newLast) / 2.0
  306. signals[i].CenterHz = d.centerFreqForBin(centerBin, centerHz)
  307. signals[i].BWHz = float64(newLast-newFirst+1) * d.binWidth
  308. }
  309. signals = d.mergeOverlapping(signals, centerHz)
  310. return signals
  311. }
  312. func (d *Detector) mergeOverlapping(signals []Signal, centerHz float64) []Signal {
  313. if len(signals) <= 1 {
  314. return signals
  315. }
  316. gapBins := 0
  317. if d.MergeGapHz > 0 && d.binWidth > 0 {
  318. gapBins = int(math.Ceil(d.MergeGapHz / d.binWidth))
  319. }
  320. sort.Slice(signals, func(i, j int) bool {
  321. return signals[i].FirstBin < signals[j].FirstBin
  322. })
  323. merged := []Signal{signals[0]}
  324. for i := 1; i < len(signals); i++ {
  325. last := &merged[len(merged)-1]
  326. cur := signals[i]
  327. gap := cur.FirstBin - last.LastBin - 1
  328. if gap <= gapBins {
  329. if cur.LastBin > last.LastBin {
  330. last.LastBin = cur.LastBin
  331. }
  332. if cur.PeakDb > last.PeakDb {
  333. last.PeakDb = cur.PeakDb
  334. }
  335. if cur.SNRDb > last.SNRDb {
  336. last.SNRDb = cur.SNRDb
  337. }
  338. centerBin := float64(last.FirstBin+last.LastBin) / 2.0
  339. last.BWHz = float64(last.LastBin-last.FirstBin+1) * d.binWidth
  340. last.CenterHz = d.centerFreqForBin(centerBin, centerHz)
  341. if cur.NoiseDb < last.NoiseDb || last.NoiseDb == 0 {
  342. last.NoiseDb = cur.NoiseDb
  343. }
  344. } else {
  345. merged = append(merged, cur)
  346. }
  347. }
  348. return merged
  349. }
  350. func (d *Detector) centerFreqForBin(bin float64, centerHz float64) float64 {
  351. return centerHz + (bin-float64(d.nbins)/2.0)*d.binWidth
  352. }
  353. func minInRange(s []float64, from, to int) float64 {
  354. if len(s) == 0 {
  355. return 0
  356. }
  357. if from < 0 {
  358. from = 0
  359. }
  360. if to >= len(s) {
  361. to = len(s) - 1
  362. }
  363. if from > to {
  364. return s[minInt(maxInt(from, 0), len(s)-1)]
  365. }
  366. m := s[from]
  367. for i := from + 1; i <= to; i++ {
  368. if s[i] < m {
  369. m = s[i]
  370. }
  371. }
  372. return m
  373. }
  374. func maxInt(a, b int) int {
  375. if a > b {
  376. return a
  377. }
  378. return b
  379. }
  380. func minInt(a, b int) int {
  381. if a < b {
  382. return a
  383. }
  384. return b
  385. }
  386. func overlapHz(center1, bw1, center2, bw2 float64) bool {
  387. left1 := center1 - bw1/2
  388. right1 := center1 + bw1/2
  389. left2 := center2 - bw2/2
  390. right2 := center2 + bw2/2
  391. return left1 <= right2 && left2 <= right1
  392. }
  393. func median(vals []float64) float64 {
  394. if len(vals) == 0 {
  395. return 0
  396. }
  397. cp := append([]float64(nil), vals...)
  398. sort.Float64s(cp)
  399. mid := len(cp) / 2
  400. if len(cp)%2 == 0 {
  401. return (cp[mid-1] + cp[mid]) / 2
  402. }
  403. return cp[mid]
  404. }
  405. func (d *Detector) makeSignal(first, last int, peak float64, peakBin int, noise float64, centerHz float64, spectrum []float64) Signal {
  406. centerBin := float64(first+last) / 2.0
  407. centerFreq := centerHz + (centerBin-float64(d.nbins)/2.0)*d.binWidth
  408. bw := float64(last-first+1) * d.binWidth
  409. snr := peak - noise
  410. return Signal{
  411. FirstBin: first,
  412. LastBin: last,
  413. CenterHz: centerFreq,
  414. BWHz: bw,
  415. PeakDb: peak,
  416. SNRDb: snr,
  417. NoiseDb: noise,
  418. }
  419. }
  420. func (d *Detector) smoothSpectrum(spectrum []float64) []float64 {
  421. if d.ema == nil || len(d.ema) != len(spectrum) {
  422. d.ema = make([]float64, len(spectrum))
  423. copy(d.ema, spectrum)
  424. return d.ema
  425. }
  426. alpha := d.EmaAlpha
  427. for i := range spectrum {
  428. v := spectrum[i]
  429. d.ema[i] = alpha*v + (1-alpha)*d.ema[i]
  430. }
  431. return d.ema
  432. }
  433. func (d *Detector) matchSignals(now time.Time, signals []Signal) []Event {
  434. used := make(map[int64]bool, len(d.active))
  435. for _, s := range signals {
  436. var best *activeEvent
  437. var candidates []struct {
  438. ev *activeEvent
  439. dist float64
  440. }
  441. for _, ev := range d.active {
  442. if overlapHz(s.CenterHz, s.BWHz, ev.centerHz, ev.bwHz) && math.Abs(s.CenterHz-ev.centerHz) < (s.BWHz+ev.bwHz)/2.0 {
  443. candidates = append(candidates, struct {
  444. ev *activeEvent
  445. dist float64
  446. }{ev: ev, dist: math.Abs(s.CenterHz - ev.centerHz)})
  447. }
  448. }
  449. if len(candidates) > 0 {
  450. sort.Slice(candidates, func(i, j int) bool { return candidates[i].dist < candidates[j].dist })
  451. best = candidates[0].ev
  452. }
  453. if best == nil {
  454. id := d.nextID
  455. d.nextID++
  456. d.active[id] = &activeEvent{
  457. id: id,
  458. start: now,
  459. lastSeen: now,
  460. centerHz: s.CenterHz,
  461. bwHz: s.BWHz,
  462. peakDb: s.PeakDb,
  463. snrDb: s.SNRDb,
  464. firstBin: s.FirstBin,
  465. lastBin: s.LastBin,
  466. class: s.Class,
  467. stableHits: 1,
  468. }
  469. continue
  470. }
  471. used[best.id] = true
  472. best.lastSeen = now
  473. best.stableHits++
  474. best.centerHz = (best.centerHz + s.CenterHz) / 2.0
  475. if s.BWHz > best.bwHz {
  476. best.bwHz = s.BWHz
  477. }
  478. if s.PeakDb > best.peakDb {
  479. best.peakDb = s.PeakDb
  480. }
  481. if s.SNRDb > best.snrDb {
  482. best.snrDb = s.SNRDb
  483. }
  484. if s.FirstBin < best.firstBin {
  485. best.firstBin = s.FirstBin
  486. }
  487. if s.LastBin > best.lastBin {
  488. best.lastBin = s.LastBin
  489. }
  490. if s.Class != nil {
  491. if best.class == nil || s.Class.Confidence >= best.class.Confidence {
  492. best.class = s.Class
  493. }
  494. }
  495. }
  496. var finished []Event
  497. for id, ev := range d.active {
  498. if used[id] {
  499. continue
  500. }
  501. if now.Sub(ev.lastSeen) < d.GapTolerance {
  502. continue
  503. }
  504. duration := ev.lastSeen.Sub(ev.start)
  505. if duration < d.MinDuration || ev.stableHits < d.MinStableFrames {
  506. delete(d.active, id)
  507. continue
  508. }
  509. finished = append(finished, Event{
  510. ID: ev.id,
  511. Start: ev.start,
  512. End: ev.lastSeen,
  513. CenterHz: ev.centerHz,
  514. Bandwidth: ev.bwHz,
  515. PeakDb: ev.peakDb,
  516. SNRDb: ev.snrDb,
  517. FirstBin: ev.firstBin,
  518. LastBin: ev.lastBin,
  519. Class: ev.class,
  520. })
  521. delete(d.active, id)
  522. }
  523. return finished
  524. }