25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

290 satır
6.9KB

  1. package detector
  2. import (
  3. "math"
  4. "sort"
  5. "time"
  6. "sdr-visual-suite/internal/classifier"
  7. )
  8. type Event struct {
  9. ID int64 `json:"id"`
  10. Start time.Time `json:"start"`
  11. End time.Time `json:"end"`
  12. CenterHz float64 `json:"center_hz"`
  13. Bandwidth float64 `json:"bandwidth_hz"`
  14. PeakDb float64 `json:"peak_db"`
  15. SNRDb float64 `json:"snr_db"`
  16. FirstBin int `json:"first_bin"`
  17. LastBin int `json:"last_bin"`
  18. Class *classifier.Classification `json:"class,omitempty"`
  19. }
  20. type Detector struct {
  21. ThresholdDb float64
  22. MinDuration time.Duration
  23. Hold time.Duration
  24. EmaAlpha float64
  25. HysteresisDb float64
  26. binWidth float64
  27. nbins int
  28. sampleRate int
  29. ema []float64
  30. active map[int64]*activeEvent
  31. nextID int64
  32. }
  33. type activeEvent struct {
  34. id int64
  35. start time.Time
  36. lastSeen time.Time
  37. centerHz float64
  38. bwHz float64
  39. peakDb float64
  40. snrDb float64
  41. firstBin int
  42. lastBin int
  43. class *classifier.Classification
  44. }
  45. type Signal struct {
  46. FirstBin int `json:"first_bin"`
  47. LastBin int `json:"last_bin"`
  48. CenterHz float64 `json:"center_hz"`
  49. BWHz float64 `json:"bw_hz"`
  50. PeakDb float64 `json:"peak_db"`
  51. SNRDb float64 `json:"snr_db"`
  52. Class *classifier.Classification `json:"class,omitempty"`
  53. }
  54. func New(thresholdDb float64, sampleRate int, fftSize int, minDur, hold time.Duration, emaAlpha, hysteresis float64) *Detector {
  55. if minDur <= 0 {
  56. minDur = 250 * time.Millisecond
  57. }
  58. if hold <= 0 {
  59. hold = 500 * time.Millisecond
  60. }
  61. if emaAlpha <= 0 || emaAlpha > 1 {
  62. emaAlpha = 0.2
  63. }
  64. if hysteresis <= 0 {
  65. hysteresis = 3
  66. }
  67. return &Detector{
  68. ThresholdDb: thresholdDb,
  69. MinDuration: minDur,
  70. Hold: hold,
  71. EmaAlpha: emaAlpha,
  72. HysteresisDb: hysteresis,
  73. binWidth: float64(sampleRate) / float64(fftSize),
  74. nbins: fftSize,
  75. sampleRate: sampleRate,
  76. ema: make([]float64, fftSize),
  77. active: map[int64]*activeEvent{},
  78. nextID: 1,
  79. }
  80. }
  81. func (d *Detector) Process(now time.Time, spectrum []float64, centerHz float64) ([]Event, []Signal) {
  82. signals := d.detectSignals(spectrum, centerHz)
  83. finished := d.matchSignals(now, signals)
  84. return finished, signals
  85. }
  86. // UpdateClasses refreshes active event classes from current signals.
  87. func (d *Detector) UpdateClasses(signals []Signal) {
  88. for _, s := range signals {
  89. for _, ev := range d.active {
  90. if overlapHz(s.CenterHz, s.BWHz, ev.centerHz, ev.bwHz) && math.Abs(s.CenterHz-ev.centerHz) < (s.BWHz+ev.bwHz)/2.0 {
  91. if s.Class != nil {
  92. if ev.class == nil || s.Class.Confidence >= ev.class.Confidence {
  93. ev.class = s.Class
  94. }
  95. }
  96. }
  97. }
  98. }
  99. }
  100. func (d *Detector) detectSignals(spectrum []float64, centerHz float64) []Signal {
  101. n := len(spectrum)
  102. if n == 0 {
  103. return nil
  104. }
  105. smooth := d.smoothSpectrum(spectrum)
  106. thresholdOn := d.ThresholdDb
  107. thresholdOff := d.ThresholdDb - d.HysteresisDb
  108. noise := median(smooth)
  109. var signals []Signal
  110. in := false
  111. start := 0
  112. peak := -1e9
  113. peakBin := 0
  114. for i := 0; i < n; i++ {
  115. v := smooth[i]
  116. if v >= thresholdOn {
  117. if !in {
  118. in = true
  119. start = i
  120. peak = v
  121. peakBin = i
  122. } else if v > peak {
  123. peak = v
  124. peakBin = i
  125. }
  126. } else if in && v < thresholdOff {
  127. signals = append(signals, d.makeSignal(start, i-1, peak, peakBin, noise, centerHz, smooth))
  128. in = false
  129. }
  130. }
  131. if in {
  132. signals = append(signals, d.makeSignal(start, n-1, peak, peakBin, noise, centerHz, smooth))
  133. }
  134. return signals
  135. }
  136. func (d *Detector) makeSignal(first, last int, peak float64, peakBin int, noise float64, centerHz float64, spectrum []float64) Signal {
  137. centerBin := float64(first+last) / 2.0
  138. centerFreq := centerHz + (centerBin-float64(d.nbins)/2.0)*d.binWidth
  139. bw := float64(last-first+1) * d.binWidth
  140. snr := peak - noise
  141. return Signal{
  142. FirstBin: first,
  143. LastBin: last,
  144. CenterHz: centerFreq,
  145. BWHz: bw,
  146. PeakDb: peak,
  147. SNRDb: snr,
  148. }
  149. }
  150. func (d *Detector) smoothSpectrum(spectrum []float64) []float64 {
  151. if d.ema == nil || len(d.ema) != len(spectrum) {
  152. d.ema = make([]float64, len(spectrum))
  153. copy(d.ema, spectrum)
  154. return d.ema
  155. }
  156. alpha := d.EmaAlpha
  157. for i := range spectrum {
  158. v := spectrum[i]
  159. d.ema[i] = alpha*v + (1-alpha)*d.ema[i]
  160. }
  161. return d.ema
  162. }
  163. func (d *Detector) matchSignals(now time.Time, signals []Signal) []Event {
  164. used := make(map[int64]bool, len(d.active))
  165. for _, s := range signals {
  166. var best *activeEvent
  167. var candidates []struct {
  168. ev *activeEvent
  169. dist float64
  170. }
  171. for _, ev := range d.active {
  172. if overlapHz(s.CenterHz, s.BWHz, ev.centerHz, ev.bwHz) && math.Abs(s.CenterHz-ev.centerHz) < (s.BWHz+ev.bwHz)/2.0 {
  173. candidates = append(candidates, struct {
  174. ev *activeEvent
  175. dist float64
  176. }{ev: ev, dist: math.Abs(s.CenterHz - ev.centerHz)})
  177. }
  178. }
  179. if len(candidates) > 0 {
  180. sort.Slice(candidates, func(i, j int) bool { return candidates[i].dist < candidates[j].dist })
  181. best = candidates[0].ev
  182. }
  183. if best == nil {
  184. id := d.nextID
  185. d.nextID++
  186. d.active[id] = &activeEvent{
  187. id: id,
  188. start: now,
  189. lastSeen: now,
  190. centerHz: s.CenterHz,
  191. bwHz: s.BWHz,
  192. peakDb: s.PeakDb,
  193. snrDb: s.SNRDb,
  194. firstBin: s.FirstBin,
  195. lastBin: s.LastBin,
  196. class: s.Class,
  197. }
  198. continue
  199. }
  200. used[best.id] = true
  201. best.lastSeen = now
  202. best.centerHz = (best.centerHz + s.CenterHz) / 2.0
  203. if s.BWHz > best.bwHz {
  204. best.bwHz = s.BWHz
  205. }
  206. if s.PeakDb > best.peakDb {
  207. best.peakDb = s.PeakDb
  208. }
  209. if s.SNRDb > best.snrDb {
  210. best.snrDb = s.SNRDb
  211. }
  212. if s.FirstBin < best.firstBin {
  213. best.firstBin = s.FirstBin
  214. }
  215. if s.LastBin > best.lastBin {
  216. best.lastBin = s.LastBin
  217. }
  218. if s.Class != nil {
  219. if best.class == nil || s.Class.Confidence >= best.class.Confidence {
  220. best.class = s.Class
  221. }
  222. }
  223. }
  224. var finished []Event
  225. for id, ev := range d.active {
  226. if used[id] {
  227. continue
  228. }
  229. if now.Sub(ev.lastSeen) < d.Hold {
  230. continue
  231. }
  232. duration := ev.lastSeen.Sub(ev.start)
  233. if duration < d.MinDuration {
  234. delete(d.active, id)
  235. continue
  236. }
  237. finished = append(finished, Event{
  238. ID: ev.id,
  239. Start: ev.start,
  240. End: ev.lastSeen,
  241. CenterHz: ev.centerHz,
  242. Bandwidth: ev.bwHz,
  243. PeakDb: ev.peakDb,
  244. SNRDb: ev.snrDb,
  245. FirstBin: ev.firstBin,
  246. LastBin: ev.lastBin,
  247. Class: ev.class,
  248. })
  249. delete(d.active, id)
  250. }
  251. return finished
  252. }
  253. func overlapHz(c1, b1, c2, b2 float64) bool {
  254. l1 := c1 - b1/2.0
  255. r1 := c1 + b1/2.0
  256. l2 := c2 - b2/2.0
  257. r2 := c2 + b2/2.0
  258. return l1 <= r2 && l2 <= r1
  259. }
  260. func median(vals []float64) float64 {
  261. if len(vals) == 0 {
  262. return 0
  263. }
  264. cpy := append([]float64(nil), vals...)
  265. sort.Float64s(cpy)
  266. mid := len(cpy) / 2
  267. if len(cpy)%2 == 0 {
  268. return (cpy[mid-1] + cpy[mid]) / 2.0
  269. }
  270. return cpy[mid]
  271. }