Wideband autonomous SDR analysis engine forked from sdr-visual-suite
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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