No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

144 líneas
2.9KB

  1. package classifier
  2. import "math"
  3. func RuleClassify(feat Features) 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. }
  15. switch {
  16. case bw >= 80e3:
  17. add(ClassWFM, 2.2)
  18. case bw >= 25e3 && bw < 80e3:
  19. add(ClassWFM, 1.4)
  20. add(ClassNFM, 0.8)
  21. case bw >= 6e3 && bw < 25e3:
  22. add(ClassNFM, 2.0)
  23. case bw >= 3e3 && bw < 6e3:
  24. add(ClassSSBUSB, 0.6)
  25. add(ClassSSBLSB, 0.6)
  26. if p2a > 2.5 && flat < 0.5 {
  27. add(ClassAM, 1.1)
  28. }
  29. case bw >= 500 && bw < 3e3:
  30. add(ClassSSBUSB, 0.8)
  31. add(ClassSSBLSB, 0.8)
  32. if p2a > 3 && flat < 0.4 {
  33. add(ClassAM, 1.0)
  34. }
  35. case bw >= 150 && bw < 500:
  36. add(ClassFSK, 0.5)
  37. add(ClassPSK, 0.5)
  38. case bw < 150:
  39. add(ClassCW, 1.6)
  40. }
  41. if sym > 0.2 {
  42. add(ClassSSBUSB, 1.2)
  43. } else if sym < -0.2 {
  44. add(ClassSSBLSB, 1.2)
  45. }
  46. if feat.EnvVariance < 0.6 && feat.InstFreqStd < 0.7 && bw >= 2000 && bw < 3000 {
  47. add(ClassFT8, 1.4)
  48. }
  49. if feat.EnvVariance < 0.4 && feat.InstFreqStd < 0.5 && bw >= 150 && bw < 500 {
  50. add(ClassWSPR, 1.3)
  51. }
  52. if feat.InstFreqStd > 0.9 {
  53. add(ClassFSK, 1.2)
  54. } else if feat.InstFreqStd < 0.25 {
  55. add(ClassPSK, 1.0)
  56. }
  57. if p2a > 2.5 && flat < 0.5 {
  58. add(ClassAM, 0.8)
  59. }
  60. if flat > 0.85 && bw > 2e3 {
  61. add(ClassNoise, 1.0)
  62. }
  63. if feat.InstFreqStd < 0.5 && feat.EnvVariance < 0.3 && bw >= 6e3 && bw < 25e3 {
  64. add(ClassDMR, 0.7)
  65. }
  66. best, bestScore, second, secondScore := top2(scores)
  67. if best == "" {
  68. best = ClassUnknown
  69. }
  70. if second == "" {
  71. second = ClassUnknown
  72. }
  73. conf := 0.3
  74. if best != ClassUnknown {
  75. sum := bestScore + secondScore + 1e-6
  76. conf = 0.3 + 0.7*(bestScore/sum)
  77. }
  78. if best == ClassNFM || best == ClassWFM {
  79. conf = conf * (0.8 + 0.2*clamp01(1-flat))
  80. }
  81. if best == ClassAM {
  82. conf = conf * (0.7 + 0.3*clamp01(p2a/6.0))
  83. }
  84. if math.IsNaN(conf) || conf <= 0 {
  85. conf = 0.3
  86. }
  87. if (best == ClassSSBUSB || best == ClassSSBLSB) && second == ClassUnknown {
  88. if best == ClassSSBUSB {
  89. second = ClassSSBLSB
  90. } else {
  91. second = ClassSSBUSB
  92. }
  93. }
  94. return Classification{
  95. ModType: best,
  96. Confidence: conf,
  97. BW3dB: bw,
  98. Features: feat,
  99. SecondBest: second,
  100. Scores: scores,
  101. }
  102. }
  103. func top2(scores map[SignalClass]float64) (SignalClass, float64, SignalClass, float64) {
  104. var best, second SignalClass
  105. bestScore := 0.0
  106. secondScore := 0.0
  107. better := func(k SignalClass, v float64, cur SignalClass, curV float64) bool {
  108. if v > curV {
  109. return true
  110. }
  111. if v < curV {
  112. return false
  113. }
  114. if cur == "" {
  115. return true
  116. }
  117. return string(k) < string(cur)
  118. }
  119. for k, v := range scores {
  120. if better(k, v, best, bestScore) {
  121. second = best
  122. secondScore = bestScore
  123. best = k
  124. bestScore = v
  125. continue
  126. }
  127. if k != best && better(k, v, second, secondScore) {
  128. second = k
  129. secondScore = v
  130. }
  131. }
  132. return best, bestScore, second, secondScore
  133. }