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

209 行
4.2KB

  1. package classifier
  2. import "math"
  3. func RuleClassify(feat Features, centerHz float64, snrDb float64) Classification {
  4. bw := feat.BW3dB
  5. flat := feat.SpectralFlat
  6. sym := feat.Symmetry
  7. p2a := feat.PeakToAvg
  8. scores := map[SignalClass]float64{}
  9. add := func(c SignalClass, w float64) {
  10. if w == 0 {
  11. return
  12. }
  13. scores[c] += w
  14. if scores[c] < 0 {
  15. scores[c] = 0
  16. }
  17. }
  18. switch {
  19. case bw >= 80e3:
  20. add(ClassWFM, 2.2)
  21. case bw >= 25e3 && bw < 80e3:
  22. add(ClassWFM, 1.4)
  23. add(ClassNFM, 0.8)
  24. case bw >= 6e3 && bw < 25e3:
  25. add(ClassNFM, 1.2)
  26. case bw >= 3e3 && bw < 6e3:
  27. add(ClassSSBUSB, 0.6)
  28. add(ClassSSBLSB, 0.6)
  29. if p2a > 2.5 && flat < 0.5 {
  30. add(ClassAM, 1.1)
  31. }
  32. case bw >= 500 && bw < 3e3:
  33. add(ClassSSBUSB, 0.8)
  34. add(ClassSSBLSB, 0.8)
  35. if p2a > 3 && flat < 0.4 {
  36. add(ClassAM, 1.0)
  37. }
  38. case bw >= 150 && bw < 500:
  39. add(ClassFSK, 0.5)
  40. add(ClassPSK, 0.5)
  41. case bw < 150:
  42. add(ClassCW, 1.6)
  43. }
  44. if sym > 0.2 {
  45. add(ClassSSBUSB, 1.2)
  46. } else if sym < -0.2 {
  47. add(ClassSSBLSB, 1.2)
  48. }
  49. rollAvg := (feat.RolloffLeft + feat.RolloffRight) / 2.0
  50. rollAsym := math.Abs(feat.RolloffLeft - feat.RolloffRight)
  51. if rollAvg > 15 && rollAsym < 5 {
  52. if bw >= 6000 {
  53. add(ClassNFM, 0.4)
  54. }
  55. if bw >= 80000 {
  56. add(ClassWFM, 0.4)
  57. }
  58. if bw >= 3000 && bw <= 10000 {
  59. add(ClassAM, 0.3)
  60. }
  61. }
  62. if rollAsym > 10 && bw >= 2000 && bw <= 4000 {
  63. if feat.RolloffLeft > feat.RolloffRight {
  64. add(ClassSSBLSB, 0.6)
  65. } else {
  66. add(ClassSSBUSB, 0.6)
  67. }
  68. }
  69. if feat.EnvVariance < 0.08 && bw >= 10000 && bw <= 14000 && flat > 0.55 {
  70. add(ClassDMR, 1.5)
  71. }
  72. if feat.EnvVariance < 0.08 && bw >= 5000 && bw <= 8000 && flat > 0.55 {
  73. add(ClassDStar, 1.3)
  74. }
  75. if feat.EnvVariance < 0.03 && bw >= 5000 && bw <= 16000 {
  76. add(ClassNFM, -0.5)
  77. }
  78. if feat.EnvVariance < 0.08 && feat.InstFreqStd < 0.7 && bw >= 2000 && bw < 3000 {
  79. add(ClassFT8, 1.4)
  80. }
  81. if feat.EnvVariance < 0.05 && feat.InstFreqStd < 0.5 && bw >= 150 && bw < 500 {
  82. add(ClassWSPR, 1.3)
  83. }
  84. if feat.InstFreqStd > 0.9 {
  85. add(ClassFSK, 1.2)
  86. } else if feat.InstFreqStd < 0.25 {
  87. add(ClassPSK, 1.0)
  88. }
  89. if p2a > 2.5 && flat < 0.5 {
  90. add(ClassAM, 0.8)
  91. }
  92. if flat > 0.85 && bw > 2e3 {
  93. add(ClassNoise, 1.0)
  94. }
  95. if feat.EnvVariance < 0.08 && feat.InstFreqStd < 0.5 && bw >= 6e3 && bw < 25e3 {
  96. add(ClassDMR, 0.7)
  97. }
  98. addFrequencyContext(add, centerHz, bw)
  99. best, _, second, _ := top2(scores)
  100. if best == "" {
  101. best = ClassUnknown
  102. }
  103. if second == "" {
  104. second = ClassUnknown
  105. }
  106. conf := softmaxConfidence(scores, best)
  107. if best == ClassNFM || best == ClassWFM {
  108. conf *= 0.8 + 0.2*clamp01(1-flat)
  109. }
  110. if best == ClassAM {
  111. conf *= 0.7 + 0.3*clamp01(p2a/6.0)
  112. }
  113. if snrDb < 20 {
  114. snrFactor := clamp01((snrDb - 3) / 17.0)
  115. conf *= 0.3 + 0.7*snrFactor
  116. }
  117. if math.IsNaN(conf) || conf <= 0 {
  118. conf = 0.1
  119. }
  120. if (best == ClassSSBUSB || best == ClassSSBLSB) && second == ClassUnknown {
  121. if best == ClassSSBUSB {
  122. second = ClassSSBLSB
  123. } else {
  124. second = ClassSSBUSB
  125. }
  126. }
  127. return Classification{
  128. ModType: best,
  129. Confidence: conf,
  130. BW3dB: bw,
  131. Features: feat,
  132. SecondBest: second,
  133. Scores: scores,
  134. }
  135. }
  136. func softmaxConfidence(scores map[SignalClass]float64, best SignalClass) float64 {
  137. if len(scores) == 0 || best == "" || best == ClassUnknown {
  138. return 0.1
  139. }
  140. maxScore := math.Inf(-1)
  141. for _, v := range scores {
  142. if v > maxScore {
  143. maxScore = v
  144. }
  145. }
  146. if math.IsInf(maxScore, -1) {
  147. return 0.1
  148. }
  149. var expSum float64
  150. var expBest float64
  151. for k, v := range scores {
  152. e := math.Exp(v - maxScore)
  153. expSum += e
  154. if k == best {
  155. expBest = e
  156. }
  157. }
  158. if expSum <= 0 {
  159. return 0.1
  160. }
  161. return expBest / expSum
  162. }
  163. func top2(scores map[SignalClass]float64) (SignalClass, float64, SignalClass, float64) {
  164. var best, second SignalClass
  165. bestScore := 0.0
  166. secondScore := 0.0
  167. better := func(k SignalClass, v float64, cur SignalClass, curV float64) bool {
  168. if v > curV {
  169. return true
  170. }
  171. if v < curV {
  172. return false
  173. }
  174. if cur == "" {
  175. return true
  176. }
  177. return string(k) < string(cur)
  178. }
  179. for k, v := range scores {
  180. if better(k, v, best, bestScore) {
  181. second = best
  182. secondScore = bestScore
  183. best = k
  184. bestScore = v
  185. continue
  186. }
  187. if k != best && better(k, v, second, secondScore) {
  188. second = k
  189. secondScore = v
  190. }
  191. }
  192. return best, bestScore, second, secondScore
  193. }