Wideband autonomous SDR analysis engine forked from sdr-visual-suite
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

233 líneas
5.1KB

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