Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

262 linhas
6.2KB

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