Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

249 рядки
5.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. 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. func (d *Detector) detectSignals(spectrum []float64, centerHz float64) []Signal {
  75. n := len(spectrum)
  76. if n == 0 {
  77. return nil
  78. }
  79. threshold := d.ThresholdDb
  80. noise := median(spectrum)
  81. var signals []Signal
  82. in := false
  83. start := 0
  84. peak := -1e9
  85. peakBin := 0
  86. for i := 0; i < n; i++ {
  87. v := spectrum[i]
  88. if v >= threshold {
  89. if !in {
  90. in = true
  91. start = i
  92. peak = v
  93. peakBin = i
  94. } else if v > peak {
  95. peak = v
  96. peakBin = i
  97. }
  98. } else if in {
  99. signals = append(signals, d.makeSignal(start, i-1, peak, peakBin, noise, centerHz, spectrum))
  100. in = false
  101. }
  102. }
  103. if in {
  104. signals = append(signals, d.makeSignal(start, n-1, peak, peakBin, noise, centerHz, spectrum))
  105. }
  106. return signals
  107. }
  108. func (d *Detector) makeSignal(first, last int, peak float64, peakBin int, noise float64, centerHz float64, spectrum []float64) Signal {
  109. centerBin := float64(first+last) / 2.0
  110. centerFreq := centerHz + (centerBin-float64(d.nbins)/2.0)*d.binWidth
  111. bw := float64(last-first+1) * d.binWidth
  112. snr := peak - noise
  113. cls := classifier.Classify(classifier.SignalInput{FirstBin: first, LastBin: last, SNRDb: snr}, spectrum, d.sampleRate, d.nbins)
  114. return Signal{
  115. FirstBin: first,
  116. LastBin: last,
  117. CenterHz: centerFreq,
  118. BWHz: bw,
  119. PeakDb: peak,
  120. SNRDb: snr,
  121. Class: cls,
  122. }
  123. }
  124. func (d *Detector) matchSignals(now time.Time, signals []Signal) []Event {
  125. used := make(map[int64]bool, len(d.active))
  126. for _, s := range signals {
  127. var best *activeEvent
  128. var candidates []struct {
  129. ev *activeEvent
  130. dist float64
  131. }
  132. for _, ev := range d.active {
  133. if overlapHz(s.CenterHz, s.BWHz, ev.centerHz, ev.bwHz) && math.Abs(s.CenterHz-ev.centerHz) < (s.BWHz+ev.bwHz)/2.0 {
  134. candidates = append(candidates, struct {
  135. ev *activeEvent
  136. dist float64
  137. }{ev: ev, dist: math.Abs(s.CenterHz - ev.centerHz)})
  138. }
  139. }
  140. if len(candidates) > 0 {
  141. sort.Slice(candidates, func(i, j int) bool { return candidates[i].dist < candidates[j].dist })
  142. best = candidates[0].ev
  143. }
  144. if best == nil {
  145. id := d.nextID
  146. d.nextID++
  147. d.active[id] = &activeEvent{
  148. id: id,
  149. start: now,
  150. lastSeen: now,
  151. centerHz: s.CenterHz,
  152. bwHz: s.BWHz,
  153. peakDb: s.PeakDb,
  154. snrDb: s.SNRDb,
  155. firstBin: s.FirstBin,
  156. lastBin: s.LastBin,
  157. class: s.Class,
  158. }
  159. continue
  160. }
  161. used[best.id] = true
  162. best.lastSeen = now
  163. best.centerHz = (best.centerHz + s.CenterHz) / 2.0
  164. if s.BWHz > best.bwHz {
  165. best.bwHz = s.BWHz
  166. }
  167. if s.PeakDb > best.peakDb {
  168. best.peakDb = s.PeakDb
  169. }
  170. if s.SNRDb > best.snrDb {
  171. best.snrDb = s.SNRDb
  172. }
  173. if s.FirstBin < best.firstBin {
  174. best.firstBin = s.FirstBin
  175. }
  176. if s.LastBin > best.lastBin {
  177. best.lastBin = s.LastBin
  178. }
  179. if s.Class != nil {
  180. if best.class == nil || s.Class.Confidence >= best.class.Confidence {
  181. best.class = s.Class
  182. }
  183. }
  184. }
  185. var finished []Event
  186. for id, ev := range d.active {
  187. if used[id] {
  188. continue
  189. }
  190. if now.Sub(ev.lastSeen) < d.Hold {
  191. continue
  192. }
  193. duration := ev.lastSeen.Sub(ev.start)
  194. if duration < d.MinDuration {
  195. delete(d.active, id)
  196. continue
  197. }
  198. finished = append(finished, Event{
  199. ID: ev.id,
  200. Start: ev.start,
  201. End: ev.lastSeen,
  202. CenterHz: ev.centerHz,
  203. Bandwidth: ev.bwHz,
  204. PeakDb: ev.peakDb,
  205. SNRDb: ev.snrDb,
  206. FirstBin: ev.firstBin,
  207. LastBin: ev.lastBin,
  208. Class: ev.class,
  209. })
  210. delete(d.active, id)
  211. }
  212. return finished
  213. }
  214. func overlapHz(c1, b1, c2, b2 float64) bool {
  215. l1 := c1 - b1/2.0
  216. r1 := c1 + b1/2.0
  217. l2 := c2 - b2/2.0
  218. r2 := c2 + b2/2.0
  219. return l1 <= r2 && l2 <= r1
  220. }
  221. func median(vals []float64) float64 {
  222. if len(vals) == 0 {
  223. return 0
  224. }
  225. cpy := append([]float64(nil), vals...)
  226. sort.Float64s(cpy)
  227. mid := len(cpy) / 2
  228. if len(cpy)%2 == 0 {
  229. return (cpy[mid-1] + cpy[mid]) / 2.0
  230. }
  231. return cpy[mid]
  232. }