Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

165 wiersze
3.3KB

  1. package classifier
  2. import (
  3. "math"
  4. )
  5. // ExtractFeatures computes spectral features for a signal slice.
  6. // spectrum is full-band power in dB (length fftSize).
  7. func ExtractFeatures(s SignalInput, spectrum []float64, sampleRate int, fftSize int) Features {
  8. if fftSize <= 0 {
  9. fftSize = len(spectrum)
  10. }
  11. if len(spectrum) == 0 || s.FirstBin < 0 || s.LastBin < s.FirstBin || s.FirstBin >= len(spectrum) {
  12. return Features{}
  13. }
  14. if s.LastBin >= len(spectrum) {
  15. s.LastBin = len(spectrum) - 1
  16. }
  17. den := fftSize
  18. if den < 1 {
  19. den = 1
  20. }
  21. binHz := float64(sampleRate) / float64(den)
  22. // slice
  23. start := s.FirstBin
  24. end := s.LastBin
  25. peakDb := -1e9
  26. peakIdx := start
  27. sumLin := 0.0
  28. geoSum := 0.0
  29. count := 0
  30. for i := start; i <= end; i++ {
  31. db := spectrum[i]
  32. if db > peakDb {
  33. peakDb = db
  34. peakIdx = i
  35. }
  36. p := math.Pow(10, db/10.0)
  37. sumLin += p
  38. if p > 0 {
  39. geoSum += math.Log(p)
  40. }
  41. count++
  42. }
  43. avgLin := 0.0
  44. if count > 0 {
  45. avgLin = sumLin / float64(count)
  46. }
  47. // Peak-to-avg in dB
  48. peakToAvg := 0.0
  49. if avgLin > 0 {
  50. peakToAvg = 10 * math.Log10(math.Pow(10, peakDb/10.0)/avgLin)
  51. }
  52. // Spectral flatness
  53. flat := 0.0
  54. if count > 0 && avgLin > 0 {
  55. geoMean := math.Exp(geoSum / float64(count))
  56. flat = geoMean / avgLin
  57. }
  58. // BW3dB
  59. bw3 := bwAtThreshold(spectrum, start, end, peakDb-3.0) * binHz
  60. // BW90 (90% energy)
  61. bw90 := bwEnergy(spectrum, start, end, 0.90) * binHz
  62. // Symmetry (power left/right of peak)
  63. leftSum, rightSum := 0.0, 0.0
  64. for i := start; i <= end; i++ {
  65. p := math.Pow(10, spectrum[i]/10.0)
  66. if i <= peakIdx {
  67. leftSum += p
  68. } else {
  69. rightSum += p
  70. }
  71. }
  72. sym := 0.0
  73. if leftSum+rightSum > 0 {
  74. sym = (rightSum - leftSum) / (rightSum + leftSum)
  75. }
  76. // Rolloff (dB/kHz) at edges
  77. leftDb := spectrum[start]
  78. rightDb := spectrum[end]
  79. leftHz := math.Max(binHz, float64(peakIdx-start)*binHz)
  80. rightHz := math.Max(binHz, float64(end-peakIdx)*binHz)
  81. rollL := (peakDb - leftDb) / (leftHz / 1e3)
  82. rollR := (peakDb - rightDb) / (rightHz / 1e3)
  83. return Features{
  84. BW3dB: bw3,
  85. BW90: bw90,
  86. SpectralFlat: clamp01(flat),
  87. PeakToAvg: peakToAvg,
  88. Symmetry: sym,
  89. RolloffLeft: rollL,
  90. RolloffRight: rollR,
  91. }
  92. }
  93. func bwAtThreshold(spectrum []float64, start, end int, threshDb float64) float64 {
  94. left := start
  95. right := end
  96. for i := start; i <= end; i++ {
  97. if spectrum[i] >= threshDb {
  98. left = i
  99. break
  100. }
  101. }
  102. for i := end; i >= start; i-- {
  103. if spectrum[i] >= threshDb {
  104. right = i
  105. break
  106. }
  107. }
  108. if right < left {
  109. return float64(end - start + 1)
  110. }
  111. return float64(right - left + 1)
  112. }
  113. func bwEnergy(spectrum []float64, start, end int, frac float64) float64 {
  114. if frac <= 0 {
  115. return 0
  116. }
  117. if frac > 1 {
  118. frac = 1
  119. }
  120. powers := make([]float64, 0, end-start+1)
  121. sum := 0.0
  122. for i := start; i <= end; i++ {
  123. p := math.Pow(10, spectrum[i]/10.0)
  124. sum += p
  125. powers = append(powers, p)
  126. }
  127. if sum == 0 {
  128. return float64(end - start + 1)
  129. }
  130. // accumulate from center outward
  131. center := (start + end) / 2
  132. l := center
  133. r := center
  134. acc := powers[center-start]
  135. for acc/sum < frac && (l > start || r < end) {
  136. if l > start {
  137. l--
  138. acc += powers[l-start]
  139. }
  140. if acc/sum >= frac {
  141. break
  142. }
  143. if r < end {
  144. r++
  145. acc += powers[r-start]
  146. }
  147. }
  148. return float64(r - l + 1)
  149. }
  150. func clamp01(v float64) float64 {
  151. if v < 0 {
  152. return 0
  153. }
  154. if v > 1 {
  155. return 1
  156. }
  157. return v
  158. }