Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

230 Zeilen
4.9KB

  1. package classifier
  2. import "math"
  3. type MathFeatures struct {
  4. EnvCoV float64 `json:"env_cov"`
  5. EnvKurtosis float64 `json:"env_kurtosis"`
  6. InstFreqStd float64 `json:"inst_freq_std"`
  7. InstFreqRange float64 `json:"inst_freq_range"`
  8. AMIndex float64 `json:"am_index"`
  9. FMIndex float64 `json:"fm_index"`
  10. InstFreqModes int `json:"inst_freq_modes"`
  11. }
  12. func ExtractMathFeatures(iq []complex64) MathFeatures {
  13. if len(iq) < 10 {
  14. return MathFeatures{}
  15. }
  16. n := len(iq)
  17. env := make([]float64, n)
  18. var envMean float64
  19. for i, v := range iq {
  20. a := math.Hypot(float64(real(v)), float64(imag(v)))
  21. env[i] = a
  22. envMean += a
  23. }
  24. envMean /= float64(n)
  25. var envVar, envM4 float64
  26. for _, a := range env {
  27. d := a - envMean
  28. envVar += d * d
  29. envM4 += d * d * d * d
  30. }
  31. envVar /= float64(n)
  32. envM4 /= float64(n)
  33. envStd := math.Sqrt(envVar)
  34. envCoV := 0.0
  35. if envMean > 1e-12 {
  36. envCoV = envStd / envMean
  37. }
  38. envKurtosis := 0.0
  39. if envVar > 1e-20 {
  40. envKurtosis = envM4 / (envVar * envVar)
  41. }
  42. instFreq := make([]float64, n-1)
  43. var ifMean float64
  44. ifMin := math.Inf(1)
  45. ifMax := math.Inf(-1)
  46. for i := 1; i < n; i++ {
  47. p := iq[i-1]
  48. c := iq[i]
  49. num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c))
  50. den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c))
  51. f := math.Atan2(num, den)
  52. instFreq[i-1] = f
  53. ifMean += f
  54. if f < ifMin {
  55. ifMin = f
  56. }
  57. if f > ifMax {
  58. ifMax = f
  59. }
  60. }
  61. ifMean /= float64(n - 1)
  62. var ifVar float64
  63. for _, f := range instFreq {
  64. d := f - ifMean
  65. ifVar += d * d
  66. }
  67. ifVar /= float64(n - 1)
  68. ifStd := math.Sqrt(ifVar)
  69. ifRange := ifMax - ifMin
  70. modes := countHistogramPeaks(instFreq, 32)
  71. amIndex := envCoV / math.Max(ifStd, 0.001)
  72. fmIndex := ifStd / math.Max(envCoV, 0.001)
  73. return MathFeatures{
  74. EnvCoV: envCoV,
  75. EnvKurtosis: envKurtosis,
  76. InstFreqStd: ifStd,
  77. InstFreqRange: ifRange,
  78. AMIndex: amIndex,
  79. FMIndex: fmIndex,
  80. InstFreqModes: modes,
  81. }
  82. }
  83. func countHistogramPeaks(vals []float64, bins int) int {
  84. if len(vals) == 0 || bins < 3 {
  85. return 0
  86. }
  87. minV, maxV := vals[0], vals[0]
  88. for _, v := range vals {
  89. if v < minV {
  90. minV = v
  91. }
  92. if v > maxV {
  93. maxV = v
  94. }
  95. }
  96. span := maxV - minV
  97. if span < 1e-10 {
  98. return 1
  99. }
  100. hist := make([]int, bins)
  101. for _, v := range vals {
  102. idx := int(float64(bins-1) * (v - minV) / span)
  103. if idx >= bins {
  104. idx = bins - 1
  105. }
  106. if idx < 0 {
  107. idx = 0
  108. }
  109. hist[idx]++
  110. }
  111. smooth := make([]int, bins)
  112. maxSmooth := 0
  113. for i := range hist {
  114. s := hist[i]
  115. if i > 0 {
  116. s += hist[i-1]
  117. }
  118. if i < bins-1 {
  119. s += hist[i+1]
  120. }
  121. smooth[i] = s
  122. if s > maxSmooth {
  123. maxSmooth = s
  124. }
  125. }
  126. peaks := 0
  127. for i := 1; i < bins-1; i++ {
  128. if smooth[i] > smooth[i-1] && smooth[i] > smooth[i+1] {
  129. if float64(smooth[i]) > 0.1*float64(maxSmooth) {
  130. peaks++
  131. }
  132. }
  133. }
  134. if peaks == 0 {
  135. peaks = 1
  136. }
  137. return peaks
  138. }
  139. func MathClassify(mf MathFeatures, bw float64, centerHz float64, snrDb float64) Classification {
  140. scores := map[SignalClass]float64{}
  141. if bw < 500 && mf.InstFreqStd < 0.15 {
  142. scores[ClassCW] += 3.0
  143. }
  144. if mf.AMIndex > 3.0 {
  145. scores[ClassAM] += 2.0
  146. } else if mf.AMIndex > 1.5 {
  147. scores[ClassAM] += 1.0
  148. }
  149. if mf.FMIndex > 5.0 && mf.EnvCoV < 0.1 {
  150. if bw >= 80e3 {
  151. scores[ClassWFM] += 2.5
  152. } else if bw >= 6e3 {
  153. scores[ClassNFM] += 2.5
  154. } else {
  155. scores[ClassNFM] += 1.5
  156. }
  157. } else if mf.FMIndex > 2.0 && mf.EnvCoV < 0.15 {
  158. if bw >= 50e3 {
  159. scores[ClassWFM] += 1.5
  160. } else {
  161. scores[ClassNFM] += 1.5
  162. }
  163. }
  164. if mf.AMIndex > 0.5 && mf.AMIndex < 3.0 && mf.FMIndex > 0.5 && mf.FMIndex < 3.0 {
  165. if bw >= 2000 && bw <= 4000 {
  166. scores[ClassSSBUSB] += 1.5
  167. scores[ClassSSBLSB] += 1.5
  168. }
  169. }
  170. if bw < 500 && mf.EnvKurtosis > 5.0 && mf.InstFreqStd < 0.1 {
  171. scores[ClassCW] += 2.5
  172. } else if bw < 200 && mf.InstFreqStd < 0.15 {
  173. scores[ClassCW] += 1.5
  174. }
  175. if bw < 500 {
  176. scores[ClassAM] *= 0.4
  177. }
  178. if mf.EnvCoV < 0.05 && mf.InstFreqModes >= 2 {
  179. if bw >= 10000 && bw <= 14000 {
  180. scores[ClassDMR] += 2.0
  181. } else if bw >= 5000 && bw <= 8000 {
  182. scores[ClassDStar] += 1.8
  183. } else {
  184. scores[ClassFSK] += 1.5
  185. }
  186. }
  187. if mf.EnvCoV < 0.08 && mf.InstFreqModes <= 1 && mf.InstFreqStd < 0.3 {
  188. if bw >= 100 && bw < 500 {
  189. scores[ClassWSPR] += 1.3
  190. }
  191. if bw >= 100 && bw < 3000 {
  192. scores[ClassPSK] += 1.0
  193. }
  194. }
  195. if mf.EnvCoV < 0.15 && mf.InstFreqModes >= 3 && bw >= 2000 && bw < 3500 {
  196. scores[ClassFT8] += 1.8
  197. }
  198. if mf.AMIndex < 0.5 && mf.FMIndex < 0.5 && bw > 2000 {
  199. scores[ClassNoise] += 1.0
  200. }
  201. best, _, second, _ := top2(scores)
  202. if best == "" {
  203. best = ClassUnknown
  204. }
  205. if second == "" {
  206. second = ClassUnknown
  207. }
  208. conf := softmaxConfidence(scores, best)
  209. if snrDb < 20 {
  210. snrFactor := clamp01((snrDb - 3) / 17.0)
  211. conf *= 0.3 + 0.7*snrFactor
  212. }
  213. if math.IsNaN(conf) || conf <= 0 {
  214. conf = 0.1
  215. }
  216. return Classification{
  217. ModType: best,
  218. Confidence: conf,
  219. BW3dB: bw,
  220. SecondBest: second,
  221. Scores: scores,
  222. MathFeatures: &mf,
  223. }
  224. }